9

I have a class like the following, that I want to unit test:

public class AddUserCommand
{
    IDbContext dbContext;

    public AddUserCommand(IDbContext context)   
    {
    dbContext = context;
    }

    public void Execute()
    {
        dbContext.Users.Add(new User());
        dbContext.SaveChanges();
    }
}

Ultimately I need to test whether the Execute method persists the new User to the database when using a real sql database connection. But for my unit tests I obviously want to use some kind of mock object. In my tests, I can make a mock IDbContext which mimics the behaviour, and it all works. I can test that the mock context contains the new user after the Execute method has been run.

My problem is that when using the mock context, the test will pass if I do not call the SaveChanges method. This is because the mock context does not need to make an sql query to actually persist the data. It 'persists' without the call to SaveChanges because the Users collection represents the persistent store.

In order to check that SaveChanges is called, many online sources (for example: http://msdn.microsoft.com/en-us/library/ff714955.aspx and http://msdn.microsoft.com/en-gb/data/dn314431.aspx) say to add something like this to the mock context:

public class MockDbContext : IDbContext
{
    boolean saved;
    public void SaveChanges {
        saved = true;
    }
}

And then test if the saved variable is true after the Execute method is called. However, what I find lacking in this approach is that such a test will pass if the Execute method did this:

public void Execute()
{
    dbContext.SaveChanges();
    dbContext.Users.Add(new User());
}

Which of course would not save any changes as it is done too early. I believe that mocking frameworks like RhinoMocks allow you to test the order of method calls to the mock context, but I have also read that this is not best practice (you should test the result, not the minutae of the implementation).

The problem is that the mock context does not exactly replicate what the real DbContext will do.

So my question is: is there a standard way to mock an entity framework DbContext in such a way that any additions or deletions of objects are only committed to the mock when SaveChanges is called? Or is this not something that is generally tested for?

Matt Hyde
  • 1,572
  • 1
  • 13
  • 17

2 Answers2

14

You should be able to do this using the Moq framework:

// Counters to verify call order
int callCount = 0;
int addUser = 0;
int saveChanges = 0;

// use Moq to create a mock IDbContext.
var mockContext = new Mock<IDbContext>();

// Register callbacks for the mocked methods to increment our counters.
mockContext.Setup(x => x.Users.Add(It.IsAny<User>())).Callback(() => addUser = callCount++);
mockContext.Setup(x => x.SaveChanges()).Callback(() => saveChanges = callCount++);

// Create the command, providing it the mocked IDbContext and execute it
var command = new AddUserCommand(mockContext.Object);
command.Execute();

// Check that each method was only called once.
mockContext.Verify(x => x.Users.Add(It.IsAny<User>()), Times.Once());
mockContext.Verify(x => x.SaveChanges(), Times.Once());

// check the counters to confirm the call order.
Assert.AreEqual(0, addUser);
Assert.AreEqual(1, saveChanges);

Following the comments about this answer, it seems that some people are missing the point of a unit test and the purpose of using abstractions within your code.

What you are doing here is verifying the behaviour of the AddUserCommand and that is all - you are confirming that the AddUserCommand class is adding a user and saving the changes on the context.

The reason to use the IDbContext interface is so that you can test the AddUserCommand class in isolation without having a database available in a known state. You don't need to test the implementation of the real DbContext because that should have it's own unit tests that cover that in isolation too.

You may also want to create an integration test where you would use the real DbContext and confirm that a record goes into the database but that is not what a unit test does.

Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
  • 2
    +1, but I wish there were a way to test that the changes had been saved, and not that `SaveChanges` was called. The latter feels like testing for implementation details and not for results. – John Saunders Feb 04 '14 at 14:45
  • 1
    @JohnSaunders - that sounds more like you want an integration tests rather than a Unit test. – Trevor Pilley Feb 04 '14 at 14:51
  • 1
    No, I want a unit test. But the fact that `SaveChanges` is called seems like a test of an implementation detail. I with I could instead test what EF does as a _result_ of `SaveChanges` being called. This would also allow for tests to ensure that an `UPDATE` will be performed if data have changed, or a `DELETE` if data were removed. – John Saunders Feb 04 '14 at 15:00
  • I don't know, something like if there were a `RowAdded` event on the context, then one could test that the `RowAdded` event was called the appropriate number of times for the appropriate rows. Testing that `SaveChanges` is called wouldn't help you if your code somehow managed to not add the entities to the collection first. – John Saunders Feb 04 '14 at 15:01
  • Something like calling `DbContext.ChangeTracker.Entries` and at least checking the `State`. – John Saunders Feb 04 '14 at 15:08
  • 1
    thanks, the answer does solve the order part of the problem. But as John says, there is still the problem that the mock does not behave as the real dbcontext does, so it isn't testing what would happen on a real database. And I do not think that requiring this makes it an integration test, as I only want to test that the code puts the user in the context (whether that is a mock context or not). If an entity framework DbContext is not completely mockable, then any code that uses it seems not to be completely unit-testable. – Matt Hyde Feb 04 '14 at 15:09
  • @JohnSaunders I agree. It would be great if there were better mocking for EF. We can argue what is/not a unit/integration test all day, but when it comes down to it, some of us just want to automate the process of "Does this code work as intended?". If using a TransactionScope and you forget to call transactionScope.Complete()? There's a bug, but with most testing practices the unit test isolates the dependencies to the point that you'll never find out that the code is not using that dependency correctly, and thus you won't discover the bug until you do manual testing. – AaronLS Feb 21 '15 at 00:39
  • I wanted to mock the Change Tracker also - SaveChanges() returns you the number of rows affected. It is reasonable to want to make sure if you add a new entity, edit one, delete one that SaveChanges returns you "3" whether or not its a mock or a real DbContext. Checking if SaveChanges() was called tests behaviour. Checking for 3 tests state. Both are equally important to verify ALL your code is working. – Ian Robertson Jul 14 '15 at 15:27
  • @IanDangerRobertson If you need to do something with the return value, just to a `mockContext.Setup(x => x.SaveChanges()).Returns(3)` – Trevor Pilley Jul 14 '15 at 15:29
  • I knew how to do that. That's me/you setting a fixed value which is of no real value. It doesn't tell anyone how many records are in the different states, like "Added", "Changed" or "Deleted". A possible way could be to mock the Add methods to increment a counter, but I dont want to have to mock many methods for EVERY DbSet that should be tracked by the change tracker. – Ian Robertson Jul 15 '15 at 09:08
  • @IanDangerRobertson a unit test is supposed to test a specific scenario, you shouldn't care about the implementation details, only the interface itself so if you have code which is dependent on the integer returned by SaveChanges then mock the result to test that scenario, if not then don't. – Trevor Pilley Jul 15 '15 at 11:01
  • I really don't agree. I am testing my own codes' implementation by wanting to check the ChangeTracker. My code is altering the entities, and I want to know that the context is collecting my changes correctly. Hard coding "3" when I might add 100 doesnt help at all. Knowing/verifying I called SaveChanges() is easy and its valuable to know the process got to the end and Saved. What would it save in a real implementation? I still care what would be saved in a real case. I want to check that before putting my application live. Thats part of knowing it actually works. Its not for "Integration test" – Ian Robertson Jul 15 '15 at 11:59
  • 1
    @IanDangerRobertson I think what TrevorPilley is trying to say is that you would need to set up multiple unit tests, each one for it's own scenario: 1) there are entities in the Add state and you got a result of x from SaveChanges, what is the code supposed to do next? 2) there are entities in the y state and you got a result of z, what is the code supposed to do next. Don't code to actually make those things happen in your Moq, make the test start from 'that already happened, now what' – James Gray Mar 23 '16 at 20:12
0

A good approach for testing interaction with a DbContext in unit tests is to use the Entity Framework Core [In-Memory provider][1].

To test whether SaveChanges has been called, you can take advantage of the fact that the in-memory provider saves the underlying data outside of the DbContext object itself. This "outside" state is only updated when one of the SaveChanges methods has been called. DbContext objects using the in-memory provider and sharing the same database name will have access to the same data.

In the following example, we give one instance of our DbContext to the command we are testing. We then use a separate DbContext to check data store state. Both are initialised with a shared database name generated within the _databaseId field (this is an XUnit test, so we will get a new test class instance and unique database name for each test that executes).

using Microsoft.EntityFrameworkCore;

namespace MyTests;

public class InMemoryDbContextExampleTests
{
    private readonly string _databaseId = Guid.NewGuid().ToString();

    [Fact]
    public void AddUserCommand_Execute_ShouldSaveNewUser()
    {
        var commandDbContext = CreateDbContext();

        var command = new AddUserCommand(commandDbContext);
        command.Execute();

        var testDbContext = CreateInMemoryDbContext();
        var user = testDbContext.Users.FirstOrDefault();
        user.Should().NotBeNull();
        // ... rest of test
    }

    private UsersDbContext CreateDbContext()
    {
        var builder = new DbContextOptionsBuilder<UsersDbContext>()
            .UseInMemoryDatabase(_databaseId);
        return new UsersDbContext(builder.Options);
    }
}

If the command didn't call SaveChanges on the commandDbContext, then the expected entity would not be present within the separate testDbContext instance.

I also find that tests like this are more meaningful. You perform state-based assertions (is the data in the state I expect?) rather than testing interactions with mock objects. That's a matter of opinion though, maybe I've been influenced by some nasty looking tests that use mocked DbContexts...
[1]: https://learn.microsoft.com/en-us/ef/core/providers/in-memory

Dan Malcolm
  • 4,382
  • 2
  • 33
  • 27