简体   繁体   English

如何使用Rhino模拟存根SingleOrDefault

[英]How to stub SingleOrDefault with Rhino mocks

I'm having a hard time understanding if I've got the right approach here. 如果我在这里有正确的方法,我很难理解。 I want to test a repository. 我想测试一个存储库。 The repository is dependent on a DbContext. 存储库依赖于DbContext。 I want to be able to verify that the repository did not call a function Add on a property of type IDbSet which is a member of the DbContext. 我希望能够验证存储库是否在IDbSet类型的属性上调用函数Add,该属性是DbContext的成员。

I've tried two approaches. 我尝试了两种方法。 Verify with behaviour, and verify with state. 验证行为,并验证状态。 Seems that verify with behaviour would be right because who knows what the stubbed state is doing in a fake object. 似乎验证行为是正确的,因为谁知道存根状态在假对象中做了什么。

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

Verify behaviour approach fails with NullReferenceExceptions around the stub of SingleOrDefault. 围绕SingleOrDefault存根的NullReferenceExceptions验证行为方法失败。 So I find posts saying best to verify state and use a fake DbSet. 所以我发现帖子说最好验证状态并使用假DbSet。 But it feels wrong to be checking state of a fake object. 但是检查假物体的状态感觉不对。 What if the Add function was implemented differently than the real one (which it was originally and my test was passing even though my repository was broken). 如果Add函数的实现方式与实际函数不同(即使我的存储库被破坏,我的测试正在通过)。

Does anyone know how to stub the SingleOrDefault so I can check Add was called? 有谁知道如何存根SingleOrDefault所以我可以检查添加被调用? I can't check Add was called on a non-rhinomocked stub. 我无法检查在非rhinomocked存根上调用Add。

Thanks 谢谢

As stated in jimmy_keen's answer : 正如jimmy_keen所说:

SingleOrDefault is an extension method defined on IEnumerable<T> (which IDbSet<T> implements). SingleOrDefault是在IEnumerable<T>IDbSet<T> implements)上定义的扩展方法。 Being extension method means it is a static method. 扩展方法意味着它是一种静态方法。 RhinoMocks (or any other free tool) cannot mock/stub static methods. RhinoMocks(或任何其他免费工具)不能模拟/存根静态方法。

Rather than trying to 'stub' the extension method, try stubbing the underlying interface that the extension method is built upon: IEnumerable<T> 而不是尝试“存根”扩展方法,尝试存根构建扩展方法的底层接口: IEnumerable<T>

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

By stubbing the behavior of GetEnumerator() when SingleOrDefault is called it will execute as expected against the fake enumeration and the test will be able to evaluate the behavior. 通过在调用SingleOrDefault时存根GetEnumerator()的行为,它将按照预期的方式执行伪计数,并且测试将能够评估行为。

SingleOrDefault is an extension method defined on IEnumerable<T> (which IDbSet<T> implements). SingleOrDefault是在IEnumerable<T>IDbSet<T> implements)上定义的扩展方法。 Being extension method means it is a static method . 扩展方法意味着它是一种静态方法 RhinoMocks (or any other free tool) cannot mock/stub static methods . RhinoMocks(或任何其他免费工具) 不能模拟/存根静态方法

Unfortunatelly you don't have many options here: you'll either have to do state-based verification, or create hand made mock and set it up manually for your test (but this most likely will end up with state-based verification as once again - you can't really stub SingleOrDefault ). 不幸的是,你在这里没有很多选择:你要么必须进行基于状态的验证,要么创建手工模拟并手动设置它以进行测试(但这很可能最终将基于状态的验证作为一次再次 - 你不能真正存根SingleOrDefault )。

Edit : extract and override example: 编辑提取和覆盖示例:

Firstly, you need to extract problematic part of your class to separate method that will be later overridden. 首先,您需要提取类的有问题的部分以分离稍后将被覆盖的方法。 This problematic part is naturally interaction with IDbSet : 这个有问题的部分自然与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);
    }
}

Now, in your testable version of EquiptmentRepository you override AddToDbSet to make testing easier, for example by simply adding some state verification: 现在,在您的可测试版本的EquiptmentRepository您可以覆盖AddToDbSetAddToDbSet测试,例如只需添加一些状态验证:

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

Whenever Add is called, passed manufacturer is going to be add to list if it would be added to DbSet in real case. 每当调用Add ,如果在实际情况下将其添加到DbSet ,则传递的制造商将被添加到列表中。 In your test, you can simply verify your testable version of the class state: 在测试中,您只需验证类状态的可测试版本:

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

This way you can verify all the logic in EquiptmentRepository.Add without the need to interact with DbSet at all. 这样,您可以验证EquiptmentRepository.Add所有逻辑,而无需与DbSet进行交互。

You can't mock static methods with RhinoMocks or Moq (TypeMock can). 你不能用RhinoMocks或Moq(TypeMock can)来模拟静态方法。

State verification on fake object actually verifies your mock, not your system under test. 虚假对象的状态验证实际上验证了您的模拟,而不是您测试的系统。

There is a technique which can make your code testable (but I think price is too high). 有一种技术可以使您的代码可测试(但我认为价格太高)。 You will have to extract extension methods to interface, and replace usage of System.Linq.Enumerable extensions with your own: 您必须提取扩展方法到接口,并用您自己的扩展替换System.Linq.Enumerable扩展的用法:

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

BTW when I face with static method mocking, I usually do one of following things: 顺便说一下,当我面对静态方法模拟时,我通常会做以下事情之一:

  • Pass result of static method execution. 传递静态方法执行的结果。 Eg if I need current date in tested method, instead of calling DateTime.Today I pass current time as parameter Foo(DateTime date) . 例如,如果我需要测试方法中的当前日期,而不是调用DateTime.Today我将当前时间作为参数Foo(DateTime date)传递。
  • Wrap static calls in some non-static object. 在一些非静态对象中包装静态调用。 Eg if I need to get some configuration settings, instead of calling ConfigurationManager.AppSettings["foo"] , I create non-static class BarConfiguration which delegates all work to static ConfigurationManager (and implements interface IBar { int Foo { get; } ). 例如,如果我需要获取一些配置设置,而不是调用ConfigurationManager.AppSettings["foo"] ,我创建非静态类BarConfiguration ,它将所有工作委托给静态ConfigurationManager(并实现接口IBar { int Foo { get; } )。
  • Do not mock it. 不要嘲笑它。 Looks like I'm testing something what I should not test (Enumerable extensions, Log, etc) 看起来我正在测试我不应该测试的东西(可枚举的扩展,日志等)

Consider not to unit-test your repositories. 考虑不对您的存储库进行单元测试。 Integration testing for data access logic makes much more sense. 数据访问逻辑的集成测试更有意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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