How do I unit test a repository that uses DbContext with NSubstitute?

Avrohom Yisroel picture Avrohom Yisroel · Jul 12, 2016 · Viewed 11.2k times · Source

I have a solution in which I have a Data project that contains an EF6 .edmx file, generated from an existing database. I split the entities into a separate Entities project, and have a Repositories project that references them both.

I have added a BaseRepository with some common methods, and want to unit test it. The top of the class 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;
  }

  //...
}

Following the code I found at https://stackoverflow.com/a/21074664/706346, I tried the following...

[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();
}

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 would guess I need to add another line when I set up my mock context, but I'm not sure what. I thought the code above should be enough to populate the DbSet.

Anyone able to explain what I've missed or done wrong?

Answer

Avrohom Yisroel picture Avrohom Yisroel · Jul 12, 2016

Well, having driven myself mad trying to do it the way I showed in my question, 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.