如何在模拟具体类时设置属性

本文关键字:设置 属性 模拟 | 更新日期: 2023-09-27 18:03:06

我试图模拟(使用Moq)在第三方SDK中定义的类和接口。下面是它们的简化示例:

public interface IVehicle
{
    string Type { get; }
}
public class Vehicle
{
    public string Type { get; }
}
public class Jeep : Vehicle, IVehicle
{
}

我可以很容易地模仿这个界面,像这样:

var mockVehicle = new Mock<IVehicule>(MockBehavior.Strict);
mockVehicle
    .SetupGet(v => v.Type)
    .Returns("My custom vehicle");

但是我需要写一个特定于Jeep类的单元测试,我不知道如何模拟它。

我的第一次尝试:

var mockJeep = new Mock<Jeep>(MockBehavior.Strict);

产生以下错误:

Moq.MockException: IVehicle.Type invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.

这是可以理解的,因为我没有设置Type属性。

第二次尝试:

var mockJeep = new Mock<Jeep>(MockBehavior.Strict);
mockJeep
    .SetupGet(v => v.Type)
    .Returns("Jeep");

产生以下错误消息:

System.NotSupportedException: Invalid setup on a non-virtual (overridable in VB) member: v => v.Type

这也是可以理解的,因为Vehicle类上的Type属性不是虚拟的。

所以我的问题是:有没有办法设置Type属性时嘲笑Jeep类?

如何在模拟具体类时设置属性

在这种情况下,Moq可以做的最好的事情是创建一个代理作为Jeep的派生类,但它不能覆盖非虚拟的Type属性。请记住,当您尝试使用Moq创建Mock时,框架会生成一个类,该类要么实现目标接口,要么从目标类派生。

Microsoft Fakes可以很好地与Moq一起工作,前提是您拥有Visual Studio许可级别来访问它。

由于这是一个相当一般化的问题,我将建议采用不同的方法。mock/fake/stub一个你无法控制的类或接口通常是困难的。此外,这通常会导致在模拟实例中复制其他人的功能。

更好的方法是将与外部库的交互隔离到一个包装类中,由您控制接口和实现。这将允许您轻松地mock/fake/stub 您的接口,以测试接口的消费者。

这仍然留下了如何处理隔离层类的问题。为此,您将需要进行更多的集成测试,该测试实际上将同时练习包装类和外部类。然后,验证您从您的类和您包装的外部类的组合中获得了预期的行为。

所以我的问题是:有没有一种方法来设置类型属性时嘲笑吉普类?

简短的回答是否定的,你不能使用Moq来Mock非虚拟成员。

然而,我不认为在这种情况下有任何理由去嘲笑任何东西,因为没有任何行为可以嘲笑。这里唯一的成员是一个简单的string属性。所以,即使你可以,你可能不应该。

  • 如果你想测试Jeep类,直接测试它不嘲笑任何成员;mock应该只在您需要控制被测系统(System Under Test, SUT)
  • 的一个依赖项的行为时使用。
  • 如果通过Jeep类绑定到另一个方法作为依赖项,以便测试该方法,您可以new启动一个Jeep实例,并在测试中通过它;因为它没有有任何跨界行为(网络调用等),然后有吗没有必要嘲笑它。

您还可以考虑,如果是第二种情况,您应该能够传递IVehicle而不是Jeep,在这种情况下,mock将重新出现(尽管如上所述,在这种情况下没有明显的需要)

顺便说一下,你的层次结构看起来有点偏离-为什么Vehicle本身不实现IVehicle ?

public interface IVehicle
{
    string Type { get; }
}
public class Vehicle: IVehicle
{
    public string Type { get; }
}
public class Jeep : Vehicle
{
}

那么Jeep将已经是一个车辆和IVehicle:)

免责声明:我在Typemock工作。

通过使用Typemock Isolator,您将能够模拟非虚拟方法,并且不需要为此更改代码,在这个特定的示例中,您可以伪造Jeep的实例,然后修改其方法行为。
下面是一个模拟Jeep的测试示例:

[TestMethod]
public void CallFakeJeepType_WillReturnWantedString()
{
    var jeep = Isolate.Fake.Instance<Jeep>();
    Isolate.WhenCalled(() => jeep.Type).WillReturn("fake jeep");
    var result = Jeep.DoSomthing(jeep);
    Assert.AreEqual("fake jeep", result);
}

注意:如果Jeep.Type也有一个setter,你可以使用typemock的True属性,测试将看起来像这样

    var jeep = Isolate.Fake.Instance<Jeep>();
    jeep.Type = "fake jeep";