Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity-Framework. Testing that SaveChanges is present and called in the correct place in a method

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?

like image 714
Matt Hyde Avatar asked Feb 04 '14 14:02

Matt Hyde


People also ask

What does the Dbcontext SaveChanges () method return?

Returns. The number of state entries written to the underlying database. This can include state entries for entities and/or relationships.

What is SaveChanges entity framework?

SaveChanges()Saves all changes made in this context to the database. This method will automatically call DetectChanges() to discover any changes to entity instances before saving to the underlying database.

Does SaveChanges commit?

In Entity Framework, the SaveChanges() method internally creates a transaction and wraps all INSERT, UPDATE and DELETE operations under it. Multiple SaveChanges() calls, create separate transactions, perform CRUD operations and then commit each transaction.

What is mocking in asp net?

A mock object is an object that can act as a real object but can be controlled in test code. Moq is a library that allows us to create mock objects in test code. It is also available in NuGet. This library also supports . NET Core.


1 Answers

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.

like image 136
Trevor Pilley Avatar answered Oct 21 '22 05:10

Trevor Pilley