Mock a DbContextFactory for Repository Unit Tests in .Net Core

In a .Net Core project, unit testing repositories with an in-memory database can be as straightforward as testing other classes. Mock the dependencies (usually the database context), inject them, and then assert on what happens. However, if you’re using the DbContextFactory class to create DbContext instances for your repository methods, you may run into problems without some additional prep.

Problem

If your repository has a shared context across the class, this is easy. Simply create the context in your unit test, inject it, and then use the context to assert what happened. But with the factory pattern, it’s not as simple. Injecting the factory itself won’t give you access to the created DbContext being used in the method being tested. So, how can we ensure we can read from the context that our repository creates for itself and then throws away without compromising the pattern we use in the repository’s implementation?

A repository class named SpinRepository that has an object called dbContextFactory injected to it. A method named UpdatePost has a statement that creates a database context using the injected factory. There is a using statement in front of the line, so that the context is disposable.
Here’s a simple repository example that uses an injected DbContextFactory.

In short, we can create a new mock DbContextFactory class for our tests. It needs to be able to do three things for us:

  1. Create a DbContext instance when asked
  2. Ensure that the created DbContext is shared between the implementation and the test
  3. Prevent the repository from disposing of the context once it’s done with it

Solution

Here’s how we can set that up.

Step 1

Our new InMemorySpinPostContextFactory class needs to implement the IDbContextFactory interface, the same interface as the _dbContextFactory variable in our repository class. The method we need to implement to satisfy our first condition is CreateDbContext. To do this, we can create a new context when it’s called, and return it. This gives us a new context when we call it, but won’t satisfy our other requirements, since the context is new every time.

public class InMemorySpinPostContextFactory : IDbContextFactory<SpinPostContext>
{

    public InMemorySpinPostContextFactory(string databaseName = "InMemoryTest")
    {
    }

    public SpinPostContext CreateDbContext()
    {
        DbContextOptions options = new DbContextOptionsBuilder()
            .UseInMemoryDatabase(databaseName)
            .Options;
        return new SpinPostContext(options);
    }
}

Step 2

To ensure we can share the context between our tests and repository, we can make some small adjustments. We can move the context creation from our CreateDbContext method into our constructor. We’ll assign this value to a new private property in the class. Then, in our CreateDbContext method, we can simply return the value of the class-level context. This achieves our second requirement, because now when the repository creates a new context when being run as part of a test, it’ll actually just be the same instance that we create when we initialize the factory.

public class InMemorySpinPostContextFactory : IDbContextFactory<SpinPostContext>
{
    private readonly SpinPostContext _dbContext;

    public InMemorySpinPostContextFactory(string databaseName = "InMemoryTest")
    {
        DbContextOptions options = new DbContextOptionsBuilder()
            .UseInMemoryDatabase(databaseName)
            .Options;
        _dbContext = new SpinPostContext(options);
    }

    public SpinPostContext CreateDbContext()
    {
        return _dbContext;
    }
}

Step 3

There’s one more requirement we need to fulfill, which becomes obvious if you run a test with our mock factory in its current state. As it stands, we’ll get a System.ObjectDisposedException if we try to use the context for assertions at the end of our test. This is because, in our implementation, we’re “using” the context, which ensures that when the method is finished, the DbContext will be disposed of. To circumvent this, we can make one more set of changes to our mock factory class.

A unit test class designed to test the UpdatePost method of a class named SpinRepository.
Here’s what my unit test looks like for this example.

The change to our factory is quite simple. Because the context is being disposed of, we need a new context class that still represents our database but can ignore any calls to be disposed of. So in our constructor, we’ll change the assignment of _dbContext to use a new TestSpinPostContext instead. This class doesn’t exist yet, so we’ll make one.

Making the Class

All that TestSpinPostContext needs to do is implement SpinPostContext, and the IDisposable interface. The former so that it can act as a SpinPostContext when needed (like in our CreateDbContext method), and the latter so that we can circumvent the disposal of the class when testing. Now all we need to do is implement an empty Dispose method, which when called will now do nothing.

public class InMemorySpinPostContextFactory : IDbContextFactory<SpinPostContext>
{
    private readonly TestSpinPostContext _dbContext;

    public InMemorySpinPostContextFactory(string databaseName = "InMemoryTest")
    {
        DbContextOptions options = new DbContextOptionsBuilder()
            .UseInMemoryDatabase(databaseName)
            .Options;
        _dbContext = new TestSpinPostContext(options);
    }

    public SpinPostContext CreateDbContext()
    {
        return _dbContext;
    }
}

public class TestSpinPostContext : SpinPostContext, IDisposable
{
    public TestSpinPostContext(DbContextOptions options) : base(options)
    {
    }

    public new void Dispose()
    {
    }
}

So finally our third requirement is met, and we can run our tests.

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *