【问题标题】:Using Moq to Mock an interface still triggers underlying method使用 Moq 模拟接口仍然会触发底层方法
【发布时间】:2013-03-26 12:16:09
【问题描述】:

我有一个类使用大致如下所示的 Web 服务:

public class MyService : IMyService
{
    private readonly IAuxilaryService1 auxilaryService1;
    private readonly IAuxilaryService2 auxilaryService2;
    private readonly IAuxilaryService3 auxilaryService3;
    private IWebService service;

    public MyService()
    {
        auxilaryService1 = new AuxilaryService1();
        auxilaryService2 = new AuxilaryService2();
        auxilaryService3 = new AuxilaryService3();
    }

    public void DoSomething(
        string Param1,
        string Param2
        )
    {
        service = new WebService(auxilaryService1, auxilaryService2, auxilaryService3); 

        var response = service.DoSomething(Param1, Param2);
        /* ... */
    }

然后是一个单元测试尝试这样做:

私有模拟网络服务;

    [SetUp]
    public void SetUp()
    {
       webService = new Mock<IWebService>();
    }

    [Test]
    public void MyService_DoSomethingTest()
    {
        IMyService myService = new MyService();

        webService.Setup(x => x.DoSomething(It.IsAny<string>(), It.IsAny<string>()))
                       .Returns(new Response() { Status = "Foo"});


        myService.DoSomething("Param1", "Param2");

        webService.Verify(x => x.DoSomething(It.IsAny<string>(), It.IsAny<string>()));
    }

首先这是测试“MyService”的好方法,其次,由于某种原因,在我的测试中接口没有被正确拦截,即使我确实要求 myService 中的 webService 返回一个虚假响应,实际实例仍然被调用,并且测试失败并出现 Soap 异常。我知道 Moq 需要接口或虚拟方法,但我提供了一个接口 Mock 为什么它不拦截它?有任何想法吗?

我需要补充一点,我正在测试自定义 umbraco 轮廓工作流类型,并且我无法控制构造函数。如果我更改它,项目将会中断。

【问题讨论】:

  • 为什么降级了?

标签: web-services unit-testing interface moq


【解决方案1】:

为了测试您的类,您应该将模拟的依赖项传递到被测类中。在您的情况下,所有依赖项都在MyService 中硬编码(它们在类中实例化)。使用依赖注入提供依赖:

public class MyService : IMyService
{
    private readonly IAuxilaryService1 auxilaryService1;
    private readonly IAuxilaryService2 auxilaryService2;
    private readonly IAuxilaryService3 auxilaryService3;
    private IWebService service;

    // constructor injection
    public MyService(IAuxilaryService1 service1,
                     IAuxilaryService2 service2,
                     IAuxilaryService3 service3)
    {
        auxilaryService1 = service1;
        auxilaryService2 = service2;
        auxilaryService3 = service3;
    }

    // pass IWebService into method
    public void DoSomething(string Param1, string Param2, IWebService service)
    {
        this.service = service;                
        var response = service.DoSomething(Param1, Param2);
        /* ... */
    }
}

现在测试应该是什么样子:

private IMyService myService;

[SetUp]
public void SetUp()
{
    IAuxilaryService1 service1 = new Mock<IAuxilaryService1>();
    IAuxilaryService2 service2 = new Mock<IAuxilaryService2>();
    IAuxilaryService3 service3 = new Mock<IAuxilaryService3>();
    myService = new MyService(service1.Object,
                              service2.Object,
                              service3.Object);       
}

[Test]
public void MyService_DoSomethingTest()
{
    // Arrange
    webService = new Mock<IWebService>();

    webService.Setup(x => x.DoSomething(It.IsAny<string>(), It.IsAny<string>()))
              .Returns(new Response() { Status = "Foo"});
    // Act
    myService.DoSomething("Param1", "Param2", webService.Object);
    // Assert
    webService.Verify(x => x.DoSomething(It.IsAny<string>(),It.IsAny<string>()));
}

顺便说一句,您在 DoSomething 方法中实例化 webService 并将 webService 分配给类字段,这很奇怪。也许您还需要通过构造函数将 webService 传递给您的类?

【讨论】:

  • 这意味着代码更加混乱。我现在必须在我的类之外实例化所有辅助服务,并在每个使用它的类中初始化 webService,以便我可以将它们作为参数传递。难道没有不需要大量重复代码的方法吗?
  • @Nick 这意味着干净的代码。依赖项应该在你的类之外实例化。类应该依赖于抽象而不是实现。
  • 假设我的服务在 20 个不同的地方使用。现在我不能再做 myService = new MyService();然后是 myService.DoSomething();。我必须在使用它的每个类中初始化所有辅助服务并初始化服务。不确定这是否更清洁。这也是很多工作
  • @Nick 是称为poor man's dependency injection的选项
  • @Nick 使用依赖注入框架,您不需要手动实例化类。请记住,当您对某个类进行单元测试时,该类应该是测试期间唯一的真实对象。所有其他依赖项都应该被模拟,如果你在类中创建一些依赖项,这是不可能的。
【解决方案2】:

您永远不会将模拟实例注入myService。您可以创建一个构造函数来注入依赖项。像这样的

  public MyService(IWebService service)
  {
      this.service = service;
  }

然后你必须在你的测试中使用这个构造函数。

【讨论】:

  • 好吧..这会起作用,但这意味着对使用此服务的现有代码进行大量重构:/
  • @Nick:如果你想模拟你必须做什么,恐怕没有魔法,你正在创建一个模拟但从不使用它。
  • @Nick:BTW:您不必更改现有的构造函数,而是添加一个新的构造函数,这样所有代码仍然可以工作
【解决方案3】:

由于这一行,实现没有使用模拟实例:

service = new WebService(auxilaryService1, auxilaryService2, auxilaryService3); 

欢迎使用依赖注入。如果你想模拟这个对象,MyService 需要请求它而不是制造它。

让你的代码看起来像这样:

private readonly IWebService webService;

public MyService(IWebService webService)
{
  this.webService = webService;
}

public void DoSomething(...)
{
   var response = webService.DoSomething(Param1, Param2);
    /* ... */
}

在您的测试中,您需要将模拟实例传递给构造函数:

IMyService myService = new MyService(webService);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-24
    • 2017-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-28
    • 1970-01-01
    相关资源
    最近更新 更多