如何正确地单元测试与Entity Framework / DbContext耦合的存储库模式方法?

时间:2017-03-20 14:50:54

标签: c# entity-framework unit-testing moq

我是单元测试的新手,并且希望编写一个N层的应用程序,因此我的ORM从BLL中抽象出来,其中存储库模式。我如何进行单元测试与EF(DbContext)耦合的存储库模式方法?

我的SUT方法在这里:

public IList<Volunteer> GetAllVolunteers() {
    return dbctx.Volunteer.ToList();
}

到目前为止我的单元测试:

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

我收到一个错误,即System.NotSupportedException:'Expression引用了一个不属于模拟对象的方法:m =&gt; m.ToList() 那么如何正确模拟DbSet以便我的假数据存在于那里以正确测试我的VolunteerRepository.GetAllVolunteers()方法是否有效?

编辑(解决方案):

var mockedDbCtxVolunteer = new Mock<DbSet<Volunteer>>();
mockedDbCtxVolunteer.As<IQueryable<Volunteer>>().Setup(m => m.GetEnumerator()).Returns(expected.GetEnumerator());

2 个答案:

答案 0 :(得分:1)

我们正在使用帮助器类来模拟DbSet。它为集合中的模拟项使用内部数据存储。

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> ...
}

然后在单元测试中,使用Moq设置datacontext。对Volunteer集的访问将被重定向到模拟实现。

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

使用MockDbSet类的优点是您不必猜测测试将访问哪些方法,因此需要进行模拟,并且您不必每次都设置模拟方法。

答案 1 :(得分:1)

ToList() is an extension method,Moq(和其他图书馆)cannot mock。不要担心嘲笑这种方法,而是模仿它会使用的方法:GetEnumerator()

var mockedCtxVolunteer = new Mock<DbSet<Volunteer>>();
mockedCtxVolunteer.Setup(m => m.GetEnumerator()).Returns(expected.GetEnumerator());