3

如果我在这里有正确的方法,我很难理解。我想测试一个存储库。存储库依赖于 DbContext。我希望能够验证存储库没有在 IDbSet 类型的属性上调用函数 Add,该属性是 DbContext 的成员。

我尝试了两种方法。用行为验证,用状态验证。似乎用行为验证是正确的,因为谁知道存根状态在假对象中做了什么。

    public void VerifyBehaviour()
    {
        // Arrange
        var stubEntities = MockRepository.GenerateStub<IWsStatContext>();
        var stubManufcturers = MockRepository.GenerateStub<IDbSet<Manufacturer>>();
        var manufacturer = new Manufacturer() { Name = "Dummy" };
        var manufacturers = new List<Manufacturer>();
        manufacturers.Add(manufacturer);

        stubManufcturers.Stub(x => x.Local).Return(new System.Collections.ObjectModel.ObservableCollection<Manufacturer>(manufacturers));
        stubManufcturers.Stub(x => x.SingleOrDefault(m => m.Name == "Dummy")).Return(manufacturer);
        stubEntities.Manufacturers = stubManufcturers;

        // Act
        var sut = new EquiptmentRepository(stubEntities);
        sut.AddManufacturer(manufacturer);

        // Assert
        stubManufcturers.AssertWasNotCalled(x => x.Add(manufacturer));
    }


    public void VerifyState()
    { 
        // Arrange
        var stubEntities = MockRepository.GenerateStub<IWsStatContext>();
        var stubManufacturers = new InMemoryDbSet<Manufacturer>();
        var manufacturer = new Manufacturer() { Name = "Dummy" };
        stubManufacturers.Add(manufacturer);
        stubEntities.Manufacturers = stubManufacturers;

        // Act
        var sut = new EquiptmentRepository(stubEntities);
        sut.AddManufacturer(manufacturer);

        // Assert
        Assert.AreEqual(stubManufacturers.Count(), 1);
    }

验证行为方法因 SingleOrDefault 存根周围的 NullReferenceExceptions 而失败。所以我发现帖子说最好验证状态并使用假的 DbSet。但是检查假对象的状态感觉不对。如果 Add 函数的实现方式与真实的不同(它最初是这样,并且我的测试通过了,即使我的存储库被破坏了)。

有谁知道如何存根 SingleOrDefault 以便我可以检查 Add 是否被调用?我无法检查 Add 是否在非 rhinomocked 存根上调用。

谢谢

4

3 回答 3

3

jimmy_keen 的回答所述:

SingleOrDefaultIEnumerable<T>是在(IDbSet<T>实现)上定义的扩展方法。作为扩展方法意味着它是一个静态方法。RhinoMocks(或任何其他免费工具)不能模拟/存根静态方法。

与其尝试“存根”扩展方法,不如尝试对构建扩展方法的底层接口进行存根:IEnumerable<T>

stubManufcturers.Stub( x => x.GetEnumerator() ).Return( new List<Manufacturer> { manufacturer }.GetEnumerator() );

GetEnumerator()通过对调用时的行为进行存根,SingleOrDefault它将按预期针对假枚举执行,并且测试将能够评估该行为。

于 2012-05-10T21:30:04.037 回答
1

SingleOrDefaultIEnumerable<T>是在(IDbSet<T>实现)上定义的扩展方法。作为扩展方法意味着它是一个静态方法。RhinoMocks(或任何其他免费工具)不能模拟/存根静态方法

不幸的是,您在这里没有太多选择:您要么必须进行基于状态的验证,要么创建手工模拟并为您的测试手动设置(但这很可能会以基于状态的验证结束)再次 - 你不能真正存根SingleOrDefault)。

编辑提取和覆盖示例:

首先,您需要提取类的有问题的部分以分离稍后将被覆盖的方法。这个有问题的部分自然是与IDbSet

public class EquiptmentRepository
{
    public void Add(Manufacturer m)
    {
        // perform some local logic before calling IDbSet.Add
        this.AddToDbSet(m);
    }

    protected virtual AddToDbSet(Manufacturer m)
    {
        this.context.Manfuacturers.Add(m);
    }
}

现在,在您的可测试版本中,EquiptmentRepository您覆盖AddToDbSet以使测试更容易,例如通过简单地添加一些状态验证:

internal void TestableEquiptmentRepository: EquiptmentRepository
{
    internal List<Manufacturer> AddedManufacturers = new List<Manufacturer>();

    protected override void AddToDbSet(Manufacturer m)
    {
        // we're no longer calling DbSet.Add but kind of rolling
        // our own basic mock and tracking what objects were
        // add by simply adding them to internal list
        this.AddedManufacturers.Add(m);
    }
}

每当被调用时,如果在实际情况下Add将添加到列表中,则传递的制造商将被添加到列表中。DbSet在您的测试中,您可以简单地验证类状态的可测试版本:

[Test]
public void AddManufacturer_DoesNotAddExistingManufacturersToDbSet()
{
    // Arrange
    var stubEntities = MockRepository.GenerateStub<IWsStatContext>();
    var stubManufacturers = MockRepository.GenerateStub<IDbSet<Manufacturer>>();
    var manufacturer = new Manufacturer() { Name = "Dummy" };
    stubManufacturers.Add(manufacturer);
    stubEntities.Manufacturers = stubManufacturers;

    // Act
    var sut = new TestableEquiptmentRepository(stubEntities);
    sut.AddManufacturer(manufacturer);

    // Assert
    Assert.AreEqual(sut.AddedManufacturers.Count(), 0);
}

通过这种方式,您可以验证所有逻辑,EquiptmentRepository.Add而无需与之交互DbSet

于 2012-05-09T23:24:41.223 回答
0

你不能用 RhinoMocks 或 Moq 模拟静态方法(TypeMock 可以)。

假对象的状态验证实际上验证了您的模拟,而不是您的被测系统。

有一种技术可以使您的代码可测试(但我认为价格太高了)。您必须将扩展方法提取到接口,并用您自己的替换 System.Linq.Enumerable 扩展的用法:

var item = items.MyExtensions().SingleOrDefault();

顺便说一句,当我面对静态方法模拟时,我通常会做以下事情之一:

  • 传递静态方法执行的结果。例如,如果我需要测试方法中的当前日期,而不是调用DateTime.Today我将当前时间作为参数传递Foo(DateTime date)
  • 将静态调用包装在一些非静态对象中。例如,如果我需要获取一些配置设置,而不是调用ConfigurationManager.AppSettings["foo"],我创建非静态类BarConfiguration,它将所有工作委托给静态 ConfigurationManager(并实现接口IBar { int Foo { get; })。
  • 不要嘲笑它。看起来我正在测试我不应该测试的东西(可枚举扩展、日志等)

考虑不要对您的存储库进行单元测试。数据访问逻辑的集成测试更有意义。

于 2012-05-10T09:52:35.837 回答