简体   繁体   中英

How do I properly unit test a repository-pattern method, that has coupling to Entity Framework / DbContext?

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

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM