【问题标题】:Providing an Implementation of an Interface for Moq to Use提供接口的实现以供 Moq 使用
【发布时间】:2018-09-15 20:19:11
【问题描述】:

假设我有以下设置:

public interface IFoo
{
    string DoSomething();
    string DoAnotherThing();
}

public sealed class Bar : IFoo
{
    public string DoAnotherThing() => "DoAnotherThing";
    public string DoSomething() => "DoSomething";
}

使用 Moq,我想模拟出Bar 的方法之一,但调用另一个方法的实现。我知道我可以通过创建一个委托给Bar 的包装类来做到这一点,如下所示:

public class MockableBar : IFoo
{
    private readonly IFoo _bar;
    public MockableBar(IFoo bar) => _bar = bar;
    public virtual string DoAnotherThing() => _bar.DoAnotherThing();
    public virtual string DoSomething() => _bar.DoSomething();
}

然后像这样嘲笑它:

var fake = new Moq.Mock<MockableBar>(new Bar()) { CallBase = true };        
fake.Setup(_ => _.DoSomething()).Returns("Mock");

Assert.AreEqual("DoAnotherThing", fake.Object.DoAnotherThing());
Assert.AreEqual("Mock", fake.Object.DoSomething());

有没有更通用的方法来完成这个,所以我不必为我想在这个机制中测试的每个接口创建一个新的包装类?

【问题讨论】:

  • 为什么需要包装类?你能不能只对Bar 进行部分模拟并获得相同的结果?
  • 为什么酒吧是密封的?这似乎是问题所在。
  • Bar 已密封,因为该类并非设计为继承自。
  • A contrary opinion. Unsealing Bar 会让您的生活变得更简单。
  • 如果在运行时自动生成代理类(类似于你的MockableBar)怎么办?虽然我同意 Michael 的观点,但毫无理由地密封课程通常会带来很多痛苦(不是给你,而是给你图书馆的用户)。我打了很多次,我需要继承以实现我的目标的第三方库中的那个类是密封的,虽然我从来没有想过“我希望那个类是密封的,如果不这样做对我来说是非常有益的能够继承它”。不过,这当然是基于意见的。

标签: c# moq


【解决方案1】:

如果我正确理解您的问题,那么您有一个实现 IFoo 的 Bar 对象。您正在测试的另一个对象依赖于 Bar,但您想用 Bar 代替假的进行测试。

怎么样:

// Arrange
Mock<IFoo> fooMock = new Mock<IFoo>();
fooMock.Setup(s => s.DoSomething()).Returns("Mock");
// using constructor injection, but hook up your IFoo implementer
// to your testObject however you normally would.
MyClass testObject = new MyClass(fooMock.Object);
// Act
testObject.DoSomethingInvokingIFooDoSomething();
// Assert whatever

你的 MyClass 对象需要使用 IFoo 而不是 Bar 作为它的参数/变量等,因为它应该被解耦,而不关心它的 IFoo 提供者是如何得出结果的,只要它可以。

【讨论】:

  • 我正在寻找一种通用的方法来解决这个问题,这样我就不必每次想要调用接口的实现时都创建可模拟类。我希望有一种方法可以创建类似Wrapper&lt;T&gt; 的东西,它需要T,我可以在任何我想使用这种模式的地方使用它。我更新了示例,让可模拟类采用IFoo,以便稍微清理一下。
  • 您的要求我不清楚。您正在测试的每件事都会对如何设置模拟有特定的要求,这排除了通用解决方案。但是,如果您有多个测试用例共享 90% 的设置,您可以创建一个助手 CreateDefaultIFooMock() 方法,然后每个测试都可以添加测试用例特定的设置。 (我已经使用 IConfig 接口完成了此操作,您可能会在给定的测试中测试特定的配置属性,但否则需要为每个接口设置合理的默认值)。
  • 我希望默认行为是调用Bar 的实现,但我希望能够覆盖该行为。我通过创建MockableBar 实现了这一点。假设我还有 10 个接口,我有类似的要求,我宁愿不必创建多个类都委托给这些接口的实现。我宁愿有一种方法来通用地创建一个默认调用实现的模拟,并且我可以在事后覆盖我需要的东西。
【解决方案2】:

我认为你错过了一些观点。不需要mockablebar。可以直接模拟IFoo接口。 (模拟也适用于虚拟方法和接口)。你在哪里需要Barobject,实际上你应该使用IFooobject。当你想测试 bar 类时,你的 sut(被测系统)对象直接是一个 bar。

注意:在测试过程中,如果你必须创建额外的类,这意味着你的代码是不可测试的。

如果你的代码中有这样的用法:

public void SomeFunction(Bar bar)
{
   .... Do something
}

你可以这样改变(并且由于这个,你可以模拟参数并轻松编写测试fot方法)

public void SomeFunction(IFoo bar)
{
   .... Do something
}

你的测试应该是这样的

Mock<IFoo> myMock = new Mock<IFoo>();

myMock.setup(m=>m.DoSomething()).Returns(()=>”some wantted result”);
//Or
myMock.setup(m=>m.DoSomething()).Throws(()=>new Exception(“some message”));

sut = new SomeClass();
sut.SomeFunction(myMock);
assert....

【讨论】:

  • 我想模拟Bar 的一部分,但将基本实现用于其他调用,因此创建了MockableBar。这些不是完全的单元测试,而是更多的子系统集成测试,我想验证Bar.DoAnotherThing() 是否在子系统处于特定状态时返回正确的值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-21
  • 1970-01-01
  • 2013-12-23
  • 1970-01-01
相关资源
最近更新 更多