【问题标题】:RhinoMock - Use a real object, but stub a single methodRhinoMock - 使用真实对象,但存根单个方法
【发布时间】:2015-06-02 23:50:43
【问题描述】:

我正在针对依赖于IFoo 的 MVC 控制器编写单元测试。 Foo(实现)有一个我想存根的方法,但我想保留另一个完整的方法。如何使用RhinoMock 进行设置?

Foo 有几个依赖项,我希望 模拟这些依赖项,以节省编写额外的代码行和弄乱我的测试。

富:

public interface IFoo{
    int Method1();
    int Method2();
}

public class Foo : IFoo{
   //lot's of dependencies
   public Foo(IBar bar, IBaz baz, IStackOverflow so){}
}

测试:

[Test]
public void What_I_Have_So_Far(){
    //arrange
    //load the real IFoo from Ninject (DI)
    var mockFoo = new Ninject.Kernel(new MyExampleModule())
                    .Get<IFoo>();

    //I want this test to use the real Method1, but not Method2
    //so stub Method2
    mockFoo
       .Stub(x => x.Method2()) //<---- blows up here
       .Returns(42);

    //act
    var controllerUnderTest = new Controller(mockFoo);

错误:

使用这种方法,RhinoMock 会抛出异常:

System.InvalidOperationException : 对象“MyApplication.MyExampleModule”不是模拟对象。

问题:

我如何存根method2

我知道我可以通过MockRepository.GenerateMock 创建IFoo 作为模拟,但是我必须复制Method1 的真实实现。


更新:

Brad 和 Jimmy 的解决方案都工作得很好,我选择了 Brad 的只是因为它编写的代码更少。

但是,在进一步研究之后,看起来我需要的是一个 AutoMocker。 StructureMapMoq 有一个接缝,但 RhinoMocks 没有:https://github.com/RhinoMocks/RhinoMocks/issues/3

【问题讨论】:

  • 好问题。你能不GenerateMock的实现类(Foo)吗?您不必复制真正的实现——它将是实际的类,但是 RhinoMocks 可以设置 method2。要么将方法标记为virtual
  • @RowanFreeman - 我一直在试验GenerateMockGeneratePartialMock,但是如果我模拟Foo 类,RhinoMock 会爆炸,因为Foo 没有无参数构造函数.所以这就引出了一个问题:如何让 RhinoMocks 与 DI 配合得很好,特别是 Ninject?
  • TestFoo : Foo 和使用 public new Int32 Method2() 隐藏原始实现怎么样(然后,显然,与 ninject 连接)。
  • @BradChristie - 原则上这是可行的(它不适用于new,原始方法必须是virtualTestFoo 必须是override 它)。但这让我很困扰,因为我编写的代码似乎比我需要的要多。并且我对需要将我的所有方法标记为虚拟并不感到兴奋。

标签: c# asp.net-mvc unit-testing rhino-mocks


【解决方案1】:

你必须反过来做。创建模拟的IFoo 并将一些 调用重定向到真实的IFoo(这必须通过WhenCalled 扩展来完成):

var realFoo = new Ninject.Kernel(new MyExampleModule()).Get<IFoo>();
var mockFoo = MockRepository.GenerateStub<IFoo>();

mockFoo.Stub(f => f.Method2()).Return(42);
mockFoo.Stub(f => f.Method1())
   .WhenCalled(invocation =>
   {
       invocation.ReturnValue = realFoo.Method2();
   })
   .Return(whateverValue);

最后的Return 是必需的,尽管我们之前已经覆盖了它几行。否则 Rhino 会抛出异常。

【讨论】:

  • 这个解决方案应该可以工作,但它仍然隐藏了糟糕的设计。看起来IFoo应该分为两个接口。 IFoo 可能会破坏Single Responsibility Principle
  • @PetrBehenský:您很可能是对的,尽管根据Method1Method2 的名字很难确定。 OP 尝试在没有覆盖方法的情况下进行部分模拟,据我所知,这是解决此问题的唯一可行解决方案(除了重新设计)。
  • @PetrBehenský - 我不同意你关于单一职责的评论。诚然,这是一个过于简化的示例,但是对于具有多个方法且不破坏单一职责的接口来说,这是完全可以的。在我的情况下,IFoo 是一个序列化/反序列化有效负载的适配器,因此它的一个职责是在 3rd 方对象和我的域对象之间进行转换。我认为将这两个拆分为 ISerializeFoo 和 IDeserializeFoo 将是矫枉过正。
  • @jimmy_keen - 有没有一种自动化的方式来构建它或一个附加的框架来做到这一点?理想情况下,我会做类似mockFoo.AutoMock(realFoo) 之类的事情,这将为Method1 连接存根,然后我可以重新存根method2
  • @PhilipPittle:我不知道。 Other frameworks, yes,RhinoMocks 没那么多。
【解决方案2】:

以最少的“侵入性”工作量,我会考虑使用适配器模式。所以,我们有了原始接口和具体类:

public interface IFoo
{
    void Method1();
    void Method2();
}

public class Foo : IFoo
{
    public void Method1()
    {
        Console.WriteLine("I am Method1 of Foo");
    }
    public void Method2()
    {
        Console.WriteLine("I am Method2 of Foo");
    }
}

还有一个用于单元测试的实现:

public class FooAdapter : IFoo
{
    private readonly IFoo foo;

    public FooAdapter(IFoo foo)
    {
        this.foo = foo;
    }

    public void Method1()
    {
        this.foo.Method1();
    }

    public void Method2()
    {
        Console.WriteLine("I am Method2 of FooAdapter");
    }
}

该类可以是单元测试类的本地类,并简单地定义为:

IFoo foo = new FooAdapter(kernel.Get<IFoo>());

我同意你的观点,改变你的基本代码来满足单元测试并不是正确的方法。单元测试应该随意放在上面,并且永远不要反向插入到他们所针对的代码中。

【讨论】:

  • 这是一种有趣的方法,但我可以看到编写和维护这真的很痛苦。除非有框架/VS插件来自动生成包装器/适配器。
【解决方案3】:

但是,在进一步研究之后,它看起来像我 需要一个 AutoMocker。 StructureMap 和 Moq 有一个接缝, 但不是 RhinoMocks:https://github.com/RhinoMocks/RhinoMocks/issues/3

我没有深入研究它,但我知道 CodePlex 上的 AutoMock 项目。也许这就是答案?

特点

  • 通过基于构造函数的依赖注入,为依赖于一组依赖项的任何被测类动态创建模拟/存根。
  • 目前支持 RhinoMocks。

https://automock.codeplex.com/

【讨论】:

  • 我浏览了一些示例,我认为这实际上正是您想要的。如果成功了请告诉我!
猜你喜欢
  • 1970-01-01
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-18
  • 2013-07-19
  • 2020-05-23
相关资源
最近更新 更多