8
\$\begingroup\$

I have WCF Service. It works fine and I want to have test coverage for it. Unit tests and acceptance.

The problem is the static class in the code. How is it possible to avoid it?

If it will be refactored to the code without static classes - I can use mocks for example Moq.

I have also used Ninject as DI Framework so far.

    [ServiceContract]
    public interface IWorkingService
    {
        [OperationContract]
        Collection<Result> UpdateEntity(int entityID);
    }

    public class WorkingService : IWorkingService
    {
        private static readonly WorkingService Logger = ProviderLog4Net.GiveLogger(typeof(SomeService));

        /// <inheritdoc />
        public Collection<Result> UpdateEntity(int entityID)
        {
            Logger.Info("------- UpdateEntity call ------------");

            try
            {
                return CoreFacade.UpdateEntity(entityID);
            }
            catch (Exception exception)
            {
                Logger.Fatal(exception);
                throw;
            }
        }           
    }

    public static class CoreFacade
    {
       ...
    }
\$\endgroup\$
1
  • \$\begingroup\$ One way might be, if possible, to change the static class into a Singleton. Though, Singletons are a completely different beast. \$\endgroup\$
    – Bobby
    Oct 28, 2013 at 9:12

3 Answers 3

14
\$\begingroup\$

You are doing it wrong.

Why are you using WCF if you are only using it as an old timer ASMX service without using the extensibility points?

First things first - clean up

[ServiceContract]
public interface IWorkingService
{
    [OperationContract]
    Collection<Ergebnis> UpdateEntity(int entityId);
}

The service interface is the same as yours, but:

  • Why are you using Collection<T> as the return type? Use someting more generic.
  • Do not use primitives as service parameters, ever! Using primitives as parameters means that you cannot versionize your service, such as adding extra non-required properties.

    public class WorkingService : IWorkingService
    {
        private readonly NotStaticFacade _notStaticFacade;
    
        public WorkingService(NotStaticFacade notStaticFacade)
        {
            _notStaticFacade = notStaticFacade;
        }
    
        public Collection<Ergebnis> UpdateEntity(int entityId)
        {
            return _notStaticFacade.UpdateEntity(entityId);
        }
    }
    

The service implementation is much more slimmer then yours because it's missing the logging stuff (be patient!). The important thing is that it isn't containing a parameter-less constructor because the service has a dependency: the NotStaticService (in your code this is the static CoreFacade). What does this mean? We will need a custom ServiceHostFactory because the default one cannot handle these kinds of situation, but don't worry; Ninject is with us!

Install-Package Ninject.Extensions.Wcf

The Ninject has an extension for WCF so install it via NuGet (remove App_Start directory after installation) and after that, create our own ServiceHostFactory:

public class WorkingServiceHostFactoryWithNinject :    NinjectServiceHostFactory
{
    private readonly IKernel _kernel;

    public WorkingServiceHostFactoryWithNinject()
    {
        _kernel = new StandardKernel(new WorkingServiceNinjectModule());

        // SetKernel is a static method in Ninject WCF!
        SetKernel(_kernel);
    }
} 

Nothing fancy; we only telling the factory which IKernel instance we want to use. As you see, you will have a NinjectModule prepared to map the bindings.

public class WorkingServiceNinjectModule : NinjectModule
{
    public override void Load()
    {
        // what ever other binding is need
        Kernel.Bind<NotStaticFacade>().ToSelf();
    }
}

I'm only telling the kernel how to resolve the NotStaticFacade but this isn't necessary this way. We haven't finished yet because we haven't told the system to use our factory to build up our service. To do this, we need to open the *.svc markup (right-click on the file then View markup) and write into the directive the Factory attribute:

<%@ ServiceHost Language="C#" Debug="true" Service="CodeReview.WorkingService" CodeBehind="WorkingService.svc.cs"
        Factory="CodeReview.WorkingServiceHostFactoryWithNinject" %>

Save and close this.

Now we have a service with dependency injection and the only one real facade is our service.

WCF extensibility - IServiceBehavior, IErrorHandler

The WCF infrastructure has great possibilities for extending its capabilities, such as handling an error!

To do that we will create a new service behavior and we will use it for adding our error handlers to the service:

public class ErrorHandlerBehaviorWithNinjectKernel : IServiceBehavior
{
    private readonly IKernel _kernel;

    public ErrorHandlerBehaviorWithNinjectKernel(IKernel kernel)
    {
        _kernel = kernel;
    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        var errorHandlers = _kernel.GetAll<IErrorHandler>().ToArray();

        if (!errorHandlers.Any())
        {
            throw new Exception("No errorhandler was found");
        }

        foreach (var channelDispatcher in serviceHostBase.ChannelDispatchers.Select(channelDispatcherBase => channelDispatcherBase as ChannelDispatcher))
        {
            foreach (var errorHandler in errorHandlers)
            {
                channelDispatcher.ErrorHandlers.Add(errorHandler);
            }
        }
    }
}

What can we see here? We are adding all IErrorHandler instance to all possible dispatchers to use it. IErrorHandler is coming from the WCF infrastructure. You need to create your own implementation of it like this:

public class ErrorLogger : IErrorHandler
{
    private readonly IAmLogger _logger;

    public ErrorLogger(IAmLogger logger)
    {
        _logger = logger;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        //nothing to do here
    }

    public bool HandleError(Exception error)
    {
        _logger.Fatal(error);

        return true;
    }
}

I have created the IAmLogger interface to fake your Log4Net stuff, nothing more:

public interface IAmLogger
{
    void Info(string info);
    void Fatal(Exception exception);
}

If you have created your IErrorHandler implementation, register it in your NinjectModule:

Kernel.Bind<IErrorHandler>().To<ErrorLogger>();

There are two ways to add an IServiceBehavior to a service: view attributes (then our class have to derive from Attribute) and programatically. We will use the last one because of the IKernel dependency.

Let's bring up the factory again and extend it with overriding the CreateServiceHost method:

public class WorkingServiceHostFactoryWithNinject : NinjectServiceHostFactory
{
    private readonly IKernel _kernel;

    public WorkingServiceHostFactoryWithNinject()
    {
        _kernel = new StandardKernel(new WorkingServiceNinjectModule());

        // SetKernel is a static method in Ninject WCF!
        SetKernel(_kernel);
    }

    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        var serviceHost = base.CreateServiceHost(serviceType, baseAddresses);

        serviceHost.Description.Behaviors.Add(new ErrorHandlerBehaviorWithNinjectKernel(_kernel));

        return serviceHost;
    }
}

Now we are creating the ServiceHost and after that we are adding our behavior (ErrorHandlerBehaviorWithNinjectKernel) to it. That is how every exception in the system will be logged and we don't need to write anything into our service.

Logging operation calls - IOperationInvoker, IOperationBehavior

In WCF we can specify our own Operation invoker by implementing the IOperationInvoker interface and applying it to our operations via an IOperationBehavior instance inside a service behavior instance. (That's all, really!)

First create an IOperationLogger implementation:

public class OperationInvokerWithLogging : IOperationInvoker
{
    private readonly IAmLogger _logger;
    private readonly IOperationInvoker _invoker;
    private readonly DispatchOperation _dispatchOperation;

    public OperationInvokerWithLogging(IAmLogger logger, IOperationInvoker invoker, DispatchOperation dispatchOperation)
    {
        _logger = logger;
        _invoker = invoker;
        _dispatchOperation = dispatchOperation;
    }

    public object[] AllocateInputs()
    {
        return _invoker.AllocateInputs();
    }

    public object Invoke(object instance, object[] inputs, out object[] outputs)
    {
        _logger.Info(string.Format("Operation {0} called (Invoke)", _dispatchOperation.Name));
        return _invoker.Invoke(instance, inputs, out outputs);
    }

    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
    {
        _logger.Info(string.Format("Operation {0} called (InvokeBegin)", _dispatchOperation.Name));
        return _invoker.InvokeBegin(instance, inputs, callback, state);
    }

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
    {
        _logger.Info(string.Format("Operation {0} called (InvokeEnd)", _dispatchOperation.Name));
        return _invoker.InvokeEnd(instance, out outputs, result);
    }

    public bool IsSynchronous { get { return _invoker.IsSynchronous; } }
}

This is mostly a stub for the real IOperationInvoker what comes from the WCF infrastructure (we will see it soon). The only additional mechism is the logging parts (with out IAmLogger instance!).

Let's create our IOperationBehavior implementation; this will map the IOperationInvoker to all of our operations:

public class LoggingOperationBehaviorWithNinjectKernel : IOperationBehavior
{
    private readonly IKernel _kernel;

    public LoggingOperationBehaviorWithNinjectKernel(IKernel kernel)
    {
        _kernel = kernel;
    }

    public void Validate(OperationDescription operationDescription)
    {

    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        dispatchOperation.Invoker = new OperationInvokerWithLogging(_kernel.Get<IAmLogger>(), dispatchOperation.Invoker, dispatchOperation);
    }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    {

    }

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
    {

    }
}

Only mapping the invoker, nothing more except that we are resolving the IAmInvoker with Ninject.

Now we need a service behavior to apply the operation behavior to our service like the error handling stuff:

public class ServiceLoggingBehavior : IServiceBehavior
{
    private readonly IKernel _kernel;

    public ServiceLoggingBehavior(IKernel kernel)
    {
        _kernel = kernel;
    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (var operation in serviceDescription.Endpoints.SelectMany(endpoint => endpoint.Contract.Operations))
        {
            operation.Behaviors.Add(new LoggingOperationBehaviorWithNinjectKernel(_kernel));
        }
    }
}

More or less the same as above nothing new here, map it in our factory and lets see what we have got:

public class WorkingServiceHostFactoryWithNinject : NinjectServiceHostFactory
{
    private readonly IKernel _kernel;

    public WorkingServiceHostFactoryWithNinject()
    {
        _kernel = new StandardKernel(new WorkingServiceNinjectModule());

        // SetKernel is a static method in Ninject WCF!
        SetKernel(_kernel);
    }

    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        var serviceHost = base.CreateServiceHost(serviceType, baseAddresses);

        serviceHost.Description.Behaviors.Add(new ErrorHandlerBehaviorWithNinjectKernel(_kernel));
        serviceHost.Description.Behaviors.Add(new ServiceLoggingBehavior(_kernel));

        return serviceHost;
    }
}

Pretty clear stuff and still no extra code in our service implementation!

About unit testing

As the others mentioned above, we are not really unit testing service implementations because, as you see, it contains a lot of infrastructure stuff in it. These are small parts which can be tested but mostly it isn't worth it.

Now you only have to test your NotStaticFacade class and not the service itself because it's just a stub. Of course it can contain code parts which are needed to be tested (for example the service using EF entities and transforms them to DTO instances) but now you can test these thing simpler:

Create an interface for the NotStaticFacade so you can fake it out with a simple implementation and modify your service constructor to accept the interface (ICoreStuff or whatever) not the concrete implementation. If you created a stub for the interface you can have unit tests to check your service output.

And of course you can still have integration tests to check the whole service behavior and in those tests you can set up different or no logger for example.

I'm not sure the things above will work as the way they are because I haven't tested it, but the main implementation steps are covered in the code examples.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ Great answer. Why not use the Ninject.Extensions.Logging extension though? It works with both NLog and Log4Net. Also not too sure about injecting a Kernel as a dependency, sounds like service locator anti-pattern; I guess the service being an infrastructure thing makes it ok. \$\endgroup\$ Nov 1, 2013 at 17:24
  • \$\begingroup\$ Yes, injecting the whole IKernel is kind of service locator stuff but i think it's fair enough in those spaces becouse they (consumer classes) will not be tested. \$\endgroup\$
    – Peter Kiss
    Nov 1, 2013 at 17:36
  • \$\begingroup\$ Great answer. Hey can you please explain (may be provide provide a link if you have) on "Do not use primitives as service parameters, ever! " much appreciated. \$\endgroup\$
    – Raj
    Feb 28, 2016 at 22:17
  • \$\begingroup\$ It is simple - you cannot "version" your service. You cannot add more parameter to any of you operations without any breaking change. If one of your client is already using your service then after you modify your operations' signature they probably cannot use your service any longer without code changes. \$\endgroup\$
    – Peter Kiss
    Jun 8, 2016 at 6:51
4
\$\begingroup\$

Personally I would not recommend writing a Unit test for a WCF service. It is an infrastructure and generally you would orchestrate routines, call a façade or a another service, provide addition infrastructure such as logging caching etc. There is no much behaviour, and you should not. I think you get more benefit writing an integration test or an acceptance test. Also doing TDD for WCF is unheard-of.

I have seen developers attempt write Unit Tests for WCF services and caught up with mocking issues, etc. And maintenance issue on both test and the code when there is a need to add additional infrastructure. Even if you were able to mock certain things as it current state, then you would test pretty much verification type testing. For example, whether a method has been called or not. We encourage more state based type testing, not verification type testing. Now you see when adding more tests means more code to maintain.

You get more value of for your effort if you Unit Test your domain, or your standard POCO classes with behaviour, or even the services (which may have behaviour) that might consume your WCF service.

\$\endgroup\$
3
  • \$\begingroup\$ Thank you for you answer! Yes I want to have acceptance/integration tests. For example test call my service and I test the result. For this purpose I have to use mocks, but it is difficult cause of static \$\endgroup\$
    – MikroDel
    Oct 28, 2013 at 14:45
  • \$\begingroup\$ You write also "I have seen developers attempt write Unit Tests for WCF services and caught up with mocking issues, etc." Thats why I have posted my code here to avoid such a problems \$\endgroup\$
    – MikroDel
    Oct 28, 2013 at 14:47
  • \$\begingroup\$ Also doing TDD for WCF is unheard-of... except for @PeterKiss excellent answer \$\endgroup\$
    – JJS
    Feb 25, 2016 at 23:36
1
\$\begingroup\$

I agree with Raj that you are probably best off not trying to unit test your WCF services themselves, but I have a suggestion specifically around your concerns regarding your static class/methods, assuming you are using VS2012 or later.

I recently had to write some unit tests for some classes that themselves called some static methods that I could not modify. What I ended up doing after some research was utilizing shims.

Here's a simple example:

public class Car
{
    public static int RetrieveTopSpeed(string carName)
    {
        //logic here
    }
}

And my test method (using MSTest):

[TestMethod]
public void TestClassRelyingOnTopSpeed()
{
    using (ShimsContext.Create())
    {
       //this will always return 10 regardless of the car name, you can of course make this smarter
        ShimCar.RetrieveTopSpeed = name => 10;

        var result = foo.MethodThatUsesRetrieveTopSpeed();
        Assert.IsTrue(result);
    }
}

My situation only included static methods on non-static classes, but it should work just the same for your situation.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.