Mocking EF Context for Unit Testing WCF Services

January 6th 2014 Entity Framework WCF Unit Testing

Units tests are all about testing a specific unit of code without any external dependencies. This makes the tests faster and less fragile, since there are no out-of-process calls and all dependencies are under the test's control. Of course, it's not always easy to remove all external dependencies. One such example is a WCF service using entity framework for database access in its operations.

It would be easy to create a test calling such a web service through a proxy (i.e. a service reference) while it is connected to an appropriate sample database. Though, that would be an integration test, not a unit test. Such tests are slow, it's difficult to setup the environment for them to run correctly, and they tend to break easily because something happened to the database or the hosted service. Wouldn't it be nice to be able to test a service without the database and without having to host it at all? Let's see how this can be done.

Our sample DbContext will have only a single entity:

public class ServiceContext : DbContext
{
    public DbSet<ErrorReportEntity> ErrorReports { get; set; }
}

public class ErrorReportEntity
{
    public int Id { get; set; }
    public string ExceptionDetails { get; set; }
    public DateTime OccuredAt { get; set; }
    public DateTime ReportedAt { get; set; }
}

The sample service will have only a single method:

[ServiceContract]
public interface IService
{
    [OperationContract]
    void ReportError(ErrorReport report);
}

public class Service : IService
{
    public void ReportError(ErrorReport report)
    {
        using (var context = new ServiceContext())
        {
            var reportEntity = new ErrorReportEntity
            {
                ExceptionDetails = report.ExceptionDetails,
                OccuredAt = report.OccuredAt,
                ReportedAt = DateTime.Now,
            };
            context.ErrorReports.Add(reportEntity);
            context.SaveChanges();
        }
    }
}

We first need an alternative implementation of ServiceContext for testing which won't require a database. This could be its interface:

public interface IServiceContext : IDisposable
{
    IDbSet<ErrorReportEntity> ErrorReports { get; set; }
    void SaveChanges();
}

Notice the use of IDbSet instead of DbSet. We also added SaveChanges to the interface since we need to call it from our service. ServiceContext now needs to implement this interface:

public class ServiceContext : DbContext, IServiceContext
{
    public IDbSet<ErrorReportEntity> ErrorReports { get; set; }

    public new void SaveChanges()
    {
        base.SaveChanges();
    }
}

For the tests we will of course have a different implementation.

public class MockServiceContext : IServiceContext
{
    public IDbSet<ErrorReportEntity> ErrorReports { get; set; }

    public MockServiceContext()
    {
        ErrorReports = new InMemoryDbSet<ErrorReportEntity>();
    }

    public void SaveChanges()
    { }

    public void Dispose()
    { }
}

You might wonder where InMemoryDbSet came from. It's an in-memory implementation of IDbSet which you can get by installing FakeDbSet NuGet package.

Having two different implementations of IServiceContext, we need a way to inject the desired one into our service for each case: MockServiceContext when testing and ServiceContext when actually hosting the service in IIS. We'll use Ninject as the dependency injection framework with the constructor injection pattern. This would be the naïve attempt at changing the service implementation:

public class Service : IService
{
    private readonly IServiceContext _context;

    public Service(IServiceContext context)
    {
        _context = context;
    }

    public bool ReportError(ErrorReport report)
    {
         var reportEntity = new ErrorReportEntity
         {
             ExceptionDetails = report.ExceptionDetails,
             OccuredAt = report.OccuredAt,
             ReportedAt = DateTime.Now,
         };
         _context.ErrorReports.Add(reportEntity);
         _context.SaveChanges();
    }
}

The downside of this approach is that we have changed the behavior. Instead of creating a new DbContext for each method call, we now use the same instance for the complete lifetime of the service. We'll see how to fix that later. First we need to make sure that we always pass the correct IServiceContext implementation to the constructor. In the test we'll do it manually:

[TestMethod]
public void ValidReport()
{
    var context = new MockServiceContext();
    var service = new Service(context);

    var error = new ErrorReport { /* initialize values */ };

    service.ReportError(error);

    var errorFromDb = context.ErrorReports
        .Single(e => e.OccuredAt == error.OccuredAt);
    // assert property values
}

For hosting in ISS we'll take advantage of WCF extensions for Ninject. NuGet package installation among other things also adds a NinjectWebCommon.cs file in App_Start folder. We need to open it and add the following line of code to the RegisterServices method inside it to register the correct IServiceContext implementation with the Ninject kernel:

kernel.Bind<IServiceContext>().To<ServiceContext>();

We only need to add the Ninject factory to the service declaration in Service.svc file, and the Service class will be correctly created – Ninject will pass it an instance of ServiceContext:

<%@ ServiceHost Language="C#" 
    Service="WebService.Service" 
    CodeBehind="Service.svc.cs" 
    Factory="Ninject.Extensions.Wcf.NinjectServiceHostFactory" %>

Now it's time to address the already mentioned issue of not instantiating a new ServiceContext for each method call. Ninject.Extensions.Factory NuGet package can help us with that. It will allow us to pass a ServiceContext factory to the service instead of passing it an already created ServiceContext. We first need a factory interface:

public interface IServiceContextFactory
{
    IServiceContext CreateContext();
}

Now we can change DbContext handling in Service back to the way it originally was:

public class Service : IService
{
    private readonly IServiceContextFactory _contextFactory;

    public Service(IServiceContextFactory contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public bool ReportError(ErrorReport report)
    {
        using (var context = _contextFactory.CreateContext())
        {
            var reportEntity = new ErrorReportEntity
            {
                ExceptionDetails = report.ExceptionDetails,
                OccuredAt = report.OccuredAt,
                ReportedAt = DateTime.Now,
            };
            context.ErrorReports.Add(reportEntity);
            context.SaveChanges();
        }
    }
}

For this to work when service is hosted in IIS, we need to additionally register the factory in RegisterServices:

kernel.Bind<IServiceContextFactory>().ToFactory();

For the test we need to implement the factory ourselves – it only takes a couple of lines:

public class MockServiceContextFactory : IServiceContextFactory
{
    public IServiceContext Context { get; private set; }

    public MockServiceContextFactory()
    {
        Context = new MockServiceContext();
    }

    public IServiceContext CreateContext()
    {
        return Context;
    }
}

At the beginning of the test we now create a factory instead of the context directly:

var contextFactory = new MockServiceContextFactory();
var service = new Service(contextFactory);

That's it. With some pretty simple refactoring we managed to get rid of all hard-coded external dependencies in our service. Writing tests is now much simpler and there is almost no code overhead when additional members are added to DbContext or the service contract.

Copyright
Creative Commons License