How to unit test a repository that uses DbContext with NSubstitute

A fairly standard set up for me is to have a solution in which I have a Data project that contains an EF6 .edmx file, generated from an existing database. I then split the entities into a separate Entities project, and have a Repositories project that references them both.

I have a generic BaseRepository with some common methods, the top of which looks like this…

public class BaseRepository<T> : BaseRepositoryInterface<T> where T : class {
  private readonly MyEntities _ctx;
  private readonly DbSet<T> _dbSet;

  public BaseRepository(MyEntities ctx) {
    _ctx = ctx;
    _dbSet = _ctx.Set<T>();
  }

  public IEnumerable<T> GetAll() {
    return _dbSet;
  }

  //...
}

The problem was, I wanted to write unit tests against the code, but couldn’t work out how to do it.

My first attempt was to do something like this…

[TestMethod]
public void BaseRepository_GetAll() {
  IDbSet<Patient> mockDbSet = Substitute.For<IDbSet<Patient>>();
  mockDbSet.Provider.Returns(GetPatients().Provider);
  mockDbSet.Expression.Returns(GetPatients().Expression);
  mockDbSet.ElementType.Returns(GetPatients().ElementType);
  mockDbSet.GetEnumerator().Returns(GetPatients().GetEnumerator());
  MyEntities mockContext = Substitute.For<MyEntities>();
  mockContext.Patients.Returns(mockDbSet);

  BaseRepositoryInterface<Patient> patientsRepository 
                          = new BaseRepository<Patient>(mockContext);
  List<Patient> patients = patientsRepository.GetAll().ToList();
  Assert.AreEqual(GetPatients().Count(), patients.Count);
}

private IQueryable<Patient> GetPatients() {
  return new List<Patient> {
    new Patient {
      ID = 1,
      FirstName = "Fred",
      Surname = "Ferret"
    }
  }
    .AsQueryable();
}

This was modelling on code I found on Stack Overflow (where else!).

Note that I changed the context TT file to use IDbSet, as suggested by Stuart Clement in his comment on Dec 4 ’15 at 22:41

However, when I run this test, it gives a null reference exception, as the line in the base repository constructor that sets _dbSet leaves it null…

_dbSet = _ctx.Set<T>();

I guessed that I needed to add another line when I set up my mock context, but wasn’t sure what. I thought the code above should be enough to populate the DbSet, but it wasn’t.

Having driven myself mad trying to do it this way, I came across Effort, which was designed for the task, and followed this tutorial, which got me going. I had a few problems with his code, which I’l explain below.

Briefly, what I did was…

*) Install Effort.EF6 in the test project. I made a mistake at first and installed Effort (without the EF6 bit), and had all sorts of problems. If you’re using EF6 (or EF5 I think), make sure you install this version.

*) Modified the MyModel.Context.tt file to include an extra constructor that took a DbConnection… public MyEntities(DbConnection connection) : base(connection, true) { }

*) Added the connection string to the test project’s App.Config file. I copied this verbatim from the data project.

*) Added an initialisation method to the test class to set up the context…

private MyEntities _ctx;
private BaseRepository<Patient> _patientsRepository;
private List<Patient> _patients;

[TestInitialize]
public void Initialize() {
  string connStr =
    ConfigurationManager.ConnectionStrings["MyEntities"].ConnectionString;
  DbConnection connection = EntityConnectionFactory.CreateTransient(connStr);
  _ctx = new MyEntities(connection);
  _patientsRepository = new BaseRepository<Patient>(_ctx);
  _patients = GetPatients();
}

Important – In the linked article, he uses DbConnectionFactory.CreateTransient(), which gave an exception when I tried to run the tests. It seems that this is for Code First, and as I’m using Model First, I had to change it to use EntityConnectionFactory.CreateTransient() instead.

*) The actual test was fairly simple. I added some helper methods to try and tidy it up, and make it more reusable. I’ll probably do several more rounds of refactoring before I’m done, but this works, and is clean enough for now…

[TestMethod]
public void BaseRepository_Update() {
  AddAllPatients();
  Assert.AreEqual(_patients.Count, _patientsRepository.GetAll().Count());
}

#region Helper methods

private List<Patient> GetPatients() {
  return Enumerable.Range(1, 10).Select(CreatePatient).ToList();
}

private static Patient CreatePatient(int id) {
  return new Patient {
    ID = id,
    FirstName = "FirstName_" + id,
    Surname = "Surname_" + id,
    Address1 = "Address1_" + id,
    City = "City_" + id,
    Postcode = "PC_" + id,
    Telephone = "Telephone_" + id
  };
}

private void AddAllPatients() {
  _patients.ForEach(p => _patientsRepository.Update(p));
}

#endregion

The bit that needed a mind-shift here was that with Effort, unlike other mocking, you don’t tell the mocking framework what to return for a particular argument. Instead, you have to think of Effort as a real database, albeit a temporary one in memory. Therefore, I set up a list of mock patients in the initialisation, added them to the database, and only then did the actual testing.

Hope this helps someone. It turned out to be a lot easier than the way I was trying to do it originally.

Be First to Comment

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.