Skip to main content

Mocking out Entity Framework Database Context with NSubstitute for your unit tests

In the realm of testing, unit test refers to testing a piece of code or logic within a method. When you are trying to test out a method, you do not typically worry about testing other modules used within the method. You are more interested to learn what is breaking within the method itself i.e. logic of the method. In this context, think about your Data Access Layer (DAL) /Module being invoked as part of your method when you are trying to do some data manipulation. In our case, this would be Entity Framework.

When we are writing our unit test against such methods, we typically also check the database via the same DAL to ensure that the data is actually persisted. But, do we really care? Think about what are we trying to test here. We are trying to test out the logic of the method to see if it works and not the DAL (on whether the DAL actually works). We trust that the DAL used here, in this case, Entity Framework, actually works. Microsoft Developers probably spend lots of time to unit test Entity Framework for us already. Thus,it is not really useful to test out Entity Framework. Secondly, having to persist actual data to your database takes time. If you have a few tests, it could be a few milliseconds. But this could really increase your testing time as your tests grows. So, this is really something to avoid. So, the solution here is to replace our DAL with a mock. Here, I am going to take about how we can accomplish this using NSubstitute. Before we go further, here are our goals:

  • Mock out our DAL level using NSubstitute.
  • Even though we are not concerned about Entity Framework actually doing work for us, we are however, concerned about the Entity Framework actually being invoked. As such, we will be using NSubstitute to ensure that certain methods Entity Framework such as Save are actually being called with parameters that we expect.
When we are using Entity Framework for our DAL, we would be accessing a class that implements DbContext with each DbSet pointing to its own table. This is a concrete implementation used. Thus, in order for mocking to work, we need to create an interface. Let's take a look at an example of what our original code would look like.

public class SomeContext : DbContext
{
    public DbSet>ClientInstance> ClientInstances { get; set; }
    public DbSet>Session> Sessions { get; set; }

    public void Save()
    {
        SaveChanges();
    }
}

Looks simple enough, we have a DbContext with 2 different DbSets.


private readonly SomeContext _someContext = new SomeContext();

public Session Connect(ClientInstance clientInstance)
{
    if (!_someContext.ClientInstances.Any(x => x.MachineName == clientInstance.MachineName && x.Id == clientInstance.Id))
        clientInstance = _someContext.ClientInstances.Add(clientInstance);

    CloseAllOpenSessions(clientInstance);

    var session = _someContext.Sessions.Add(new Session
    {
        Id = Guid.NewGuid(),
        ClientInstance = clientInstance,
        Start = DateTime.UtcNow
    });

    _someContext.Save();

    return session;
}

private void CloseAllOpenSessions(ClientInstance clientInstance)
{
    var openSessions = _someContext.Sessions.Where(x => x.ClientInstance.MachineName == clientInstance.MachineName && x.ClientInstance.Id == clientInstance.Id && x.End == null).ToList();
    if (openSessions.Count > 0)
        openSessions.ForEach(openSession => openSession.End = DateTime.UtcNow);
}

The method we are testing is called Connect. It is suppose to add a client instance if it does not already exist. Then, it is going to be closing all open sessions and creating a new session for the new connection. In order to substitute our DbContext, we will need to replace SomeContext with an interface. Let's take a look at the new interface we are creating.

public interface ISomeContext
{
    DbSet>ClientInstance> ClientInstances { get; set; }
    DbSet>Session> Sessions { get; set; }

    void Save();
}

So, now, all we have to do is to apply this interface to SomeContext.

    
public class SomeContext : DbContext, ISomeContext
{
    public DbSet>ClientInstance> ClientInstances { get; set; }
    public DbSet>Session> Sessions { get; set; }

    public void Save()
    {
        SaveChanges();
    }
}

Now, we can use dependency injection to inject our DAL. This would also mean the usage of _someContext is now abstracted away and we can easily replace _someContext with a mock!

public class ClientInstanceManager : IClientInstanceManager
{
    private readonly ISomeContext _someContext;
    public ClientInstanceManager(ISomeContext someContext)
    {
        _someContext = someContext;
}


So, what we are trying to test in this code logic are 2 things. First, we will need to ensure that the client instance is added only when it does not exist already. Secondly, we will need to ensure that if there are any old sessions, they are closed first before we open a new one. The following unit test demonstrates that.

[SetUp]
public void Setup()
{
    _someContext = Substitute.For<ISomenContext>();
    _clientInstanceManager = new ClientInstanceManager(_someContext);
}

[Test]
public void Connect_CreateNewSessionAndCloseOldOpenSessions()
{
    var clientInstance = new ClientInstance { Id = "123", MachineName = "abc" };
    SetupClientInstanceData(new List<ClientInstance> { clientInstance });

    var openSessionId = Guid.NewGuid();

    // Setup an existing session.
    var sessions = new List>Session> { new Session { ClientInstance = clientInstance, Id = openSessionId, Start = DateTime.UtcNow } };
    SetupSessionData(sessions);

    Guid newlyAddedSession = Guid.Empty;
    // Capture the fact that a new session is created and added.
    _someContext.Sessions.When(session =>
    {
        session.Add(Arg.Is>Session>(x => x.ClientInstance.Id == "123" && x.ClientInstance.MachineName == "abc"));
    }).Do((callInfo) =>
    {
        var session = callInfo.Arg>Session>();
        newlyAddedSession = session.Id;
        sessions.Add(session);
    });

    _clientInstanceManager.Connect(clientInstance);

    // Ensure that we have 2 sessions in the system.
    Assert.That(sessions.Count, Is.EqualTo(2));

    var sessionToBeClosed = sessions.Single(x => x.ClientInstance.Id == "123" && x.Id == openSessionId);
    Assert.That(sessionToBeClosed.End, Is.Not.Null);

    var sessionToBeStillOpened = sessions.Single(x => x.ClientInstance.Id == "123" && x.Id == newlyAddedSession);
    Assert.That(sessionToBeStillOpened.End, Is.Null);
}

private void SetupSessionData(List<Session> sessions)
{
    var queryable = sessions.AsQueryable();
    var mock = Substitute.For<IDbSet<Session>, DbSet<Session>>().Initialize(queryable);
    _someContext.Sessions.Returns(mock);
}

private void SetupClientInstanceData(List<Clientinstance> clientInstances)
{
    var queryable = clientInstances.AsQueryable();
    var mock = Substitute.For<IDbSet<ClientInstance>, DbSet<ClientInstance>>().Initialize(queryable);
    _someContext.ClientInstances.Returns(mock);
}

Here are some points of interest.

  • In the Setup code, we are injecting our mock instance which is created using NSubsitute's For method.
  • We are creating our own list of ClientInstance or Sessions and passing it into the mock to be used as a repository. We can see a Initialize method is being used. This is actually own own implementation and we can take a look at how we are initializing the data later.  Note that we are passing back a mock IQueryable instance.
  • Since we are getting a IQueryable instance, the Add method is not going work. As such, if we want to simulate an add, we are using the When/Do methods of NSubsitute. When we are invoking the add method, we are doing/performing a piece of logic to acquire the argument which we can then add to our own list for assertions later.
Finally, here's our extension Initialize method that provides the magic sauce to make all these work!

    
public static class Extensions
{
    public static IDbSet<T> Initialize<T>(this IDbSet<T> dbSet, IQueryable<T> data) where T : class
    {
        dbSet.Provider.Returns(data.Provider);
        dbSet.Expression.Returns(data.Expression);
        dbSet.ElementType.Returns(data.ElementType);
        dbSet.GetEnumerator().Returns(data.GetEnumerator());
        return dbSet;
    }
}


So, we can see that it is fairly easy to mock our our DAL layer so it does not involve any actual calls to the database which is not what we are trying to test. In fact, we have also saved some time (and complexities) by not persisting any data to the database. Credits for this post also goes to http://stackoverflow.com/questions/21069986/nsubstitute-dbset-iqueryablet where the magic sauce was proposed!

Comments