Reputation: 189
I'm new to unit testing and wish to write an application that is N-tier, so my ORM is abstracted away from BLL with a repository-pattern inbetween. How would I go about unit testing a repository-pattern method that has coupling to EF (DbContext)?
My SUT method is here:
public IList<Volunteer> GetAllVolunteers() {
return dbctx.Volunteer.ToList();
}
My unit test so far:
[Fact]
public void GetAllVolunteersMethodWorks() {
// Arrange
var fakeVolunteer = new Volunteer { Id = 0, Name = "Dummy" };
var expected = new List<Volunteer>(new Volunteer[] { fakeVolunteer });
var mockedCtxVolunteer = new Mock<DbSet<Volunteer>>();
mockedCtxVolunteer.Setup(m => m.ToList()).Returns(expected);
var mockedctx = new Mock<RSMContext>();
mockedctx.Setup(m => m.Volunteer).Returns(mockedCtxVolunteer.Object);
// Act
var volunteerRepo = new VolunteerRepository(mockedctx.Object);
var result = volunteerRepo.GetAllVolunteers();
// Assert
Assert.Equal(expected, result);
}
I get an error that System.NotSupportedException: 'Expression references a method that does not belong to the mocked object: m => m.ToList() So how do I properly mock the DbSet so my fake data resides there to properly test my VolunteerRepository.GetAllVolunteers() method works?
edit (solution):
var mockedDbCtxVolunteer = new Mock<DbSet<Volunteer>>();
mockedDbCtxVolunteer.As<IQueryable<Volunteer>>().Setup(m => m.GetEnumerator()).Returns(expected.GetEnumerator());
Upvotes: 0
Views: 2008
Reputation: 9463
We are using a helper class to mock the DbSet. It uses an internal data store for the mocked items in the set.
public class MockDbSet<T> : IDbSet<T> where T : class {
readonly HashSet<T> _data;
readonly IQueryable _query;
public MockDbSet() : this(new HashSet<T>()) {
}
public MockDbSet(params T[] entries) : this(new HashSet<T>(entries)) {
}
private MockDbSet(IEnumerable<T> data) {
_data = new HashSet<T>(data);
_query = _data.AsQueryable();
}
public T Add(T item) {
_data.Add(item);
return item;
}
public T Remove(T item) {
_data.Remove(item);
return item;
}
Type IQueryable.ElementType {
get { return _query.ElementType; }
}
IEnumerator IEnumerable.GetEnumerator() {
return _data.GetEnumerator();
}
// implement other members of IDbSet<T> ...
}
Then in your unit test, use Moq to set up the datacontext. Access to the Volunteer
set will be redirected to the mock implementation.
var mockedVolunteer1 = new Volunteer { Name = "Uno" };
var mockedDbSet = new MockDbSet<Volunteer> {
mockedVolunteer1, mockedVolunteer2, mockedVolunteer3
};
var mockedContext = new Mock<MyDataContext>();
mockedContext.Setup(ctx => ctx.Volunteer).Returns(mockedDbSet);
Using the MockDbSet class has the advantages that you don't have to guess which methods will be accessed by your test and therefore need mocking, and you don't have to setup the mocked methods every time.
Upvotes: 1
Reputation: 23747
ToList()
is an extension method, which Moq (and other libraries) cannot mock. Don't worry about mocking that method, but instead mock what it would use: GetEnumerator()
:
var mockedCtxVolunteer = new Mock<DbSet<Volunteer>>();
mockedCtxVolunteer.Setup(m => m.GetEnumerator()).Returns(expected.GetEnumerator());
Upvotes: 2