Mocking DBSet,EF Model First

时间:2013-03-20 06:13:37

标签: unit-testing asp.net-mvc-4 nunit entity-framework-5 rhino-mocks

如标题中所述,我遵循Model First方法。所以我的Model类是自动生成的。如果我想要模拟包含DBContext实体类的MyModelContainer派生DBSets。阅读一些内容以便进行单元测试,您需要将其更改为IDBSet。是否可以这样做,特别是在我执行“运行自定义工具”时自动生成的类是一个问题。但截至目前我修改了它。

但真正的问题是:当我尝试使用Stub MyModelContainer返回从IDBSet生成的模拟时。 Rhino mock正在触发InvalidOperationException:“无效调用,最后一次调用已被使用,或者没有调用(请确保您正在调用虚拟(C#)/ Overridable(VB)方法。”

这是我的单元测试代码。

MyModelContainer dbMock = MockRepository.GenerateMock<MyModelContainer>();
IDBSet<Models.MyEntity> entityMock = MockRepository.GenerateMock<IDBSet<Models.MyEntity>>()
dbMock.Stub( x=>x.MyEntities ).Return( entityMock );

最后一个语句是触发异常。我尝试使用IDBSet<>指定here的虚假实现,但没有运气!

我使用MVC 4,Rhino Mocks 3.6。任何帮助将不胜感激。

更新

经过一些试验和研究,我找到了解决办法。我将代码更改为:

MyModelContainer dbMock = MockRepository.GenerateMock<MyModelContainer>();
IDBSet<Models.MyEntity> entityMock = MockRepository.GenerateMock<IDBSet<Models.MyEntity>>()
//dbMock.Stub( x=>x.MyEntities ).Return( entityMock );
dbMock.MyEntities = entityMock;

现在InvalidOperationException消失了。 测试失败的原因仅在于ExpectationViolationException,这应该是正常的。

对于自动生成的Model类,我们发现编辑DbContext's T4模板(.tt扩展名)可以解决问题。感谢Alan's Blog

但我想知道为什么以前的代码不起作用。任何人吗?

3 个答案:

答案 0 :(得分:2)

这里有两个理由:

    {li>

    MyEntites MyModelContainer属性不是虚拟的 在那种情况下,Rhino Mock根本不能存在这个属性。然后dbMock.Stub(x=>x.MyEntities)将失败。

  1. MyEntites属性是虚拟的,但同时包含公共getter和public setter 然后不允许使用符号dbMock.Stub(x=>x.MyEntities).Return(entityMock)。你可以看到解释,例如here

  2. 在这两种情况下,正确的解决方案正是您所做的:使用dbMock.MyEntities = entityMock代替dbMock.Stub(x=>x.MyEntities).Return(entityMock)

答案 1 :(得分:1)

这是Stubing(使用RhinoMocks)IDbSet返回IQueryable的扩展方法

public static class RhinoExtensions
{
    public static IDbSet<T> MockToDbSet<T>(this IQueryable<T> queryable) where T : class
    {
        IDbSet<T> mockDbSet = MockRepository.GenerateMock<IDbSet<T>>();
        mockDbSet.Stub(m => m.Provider).Return(queryable.Provider);
        mockDbSet.Stub(m => m.Expression).Return(queryable.Expression);
        mockDbSet.Stub(m => m.ElementType).Return(queryable.ElementType);
        mockDbSet.Stub(m => m.GetEnumerator()).Return(queryable.GetEnumerator());
        return mockDbSet;
    }
}

然后你可以像这样存根DbContext:

_db.Stub(p => p.Customers).Return(fakeCustomers.MockToDbSet());

答案 2 :(得分:1)

Here is an extension method for Substituting IDbSet (with NSubstitute) to return an IQueryable

    public static DbSet<T> FakeDbSet<T>(this IQueryable<T> queryable) where T : class
    {
        DbSet<T> fakeDbSet = Substitute.For<DbSet<T>, IQueryable<T>>();
        ((IQueryable<T>)fakeDbSet).Provider.Returns(queryable.Provider);
        ((IQueryable<T>)fakeDbSet).Expression.Returns(queryable.Expression);
        ((IQueryable<T>)fakeDbSet).ElementType.Returns(queryable.ElementType);
        ((IQueryable<T>)fakeDbSet).GetEnumerator().Returns(queryable.GetEnumerator());
        fakeDbSet.AsNoTracking().Returns(fakeDbSet);
        return fakeDbSet;
    }

Then you can now stub the DbContext like this:

        var db = NSubstitute.Substitute.For<DataContext>();
        var fakeResult = emptyCustomers.FakeDbSet();
        db.Customers.Returns(fakeResult);