如何使用Moq模拟Web服务调用?

8

下面的using会访问一个我不想真正访问的外部资源。我想测试someResult以及使用它的代码,但每次运行单元测试时,这段代码仍会尝试访问真实的Web服务。如何使用Moq来模拟对Web服务的真实调用,而不是模拟using内部的其余代码?

public IMyInterface.SomeMethod()
{    
     // hits a web service
     using ( mySoapClient client = new mySoapClient() )
     {
          var someResult = client.DoSomething();
       ...
       ...
     }
}


[TestMethod()]
public void SomeMethodTest()
{
    IMyInterface target = new MyInterface();
    target.SomeMethod();

    // Assert....
}
3个回答

15

您需要将Web服务实现与消费者解耦

public class ClassIWantToTest
{
      public ClassIWantToTest(IServiceIWantToCall service) {}

      public void SomeMethod()
      {
           var results = service.DoSomething();
           //Rest of the logic here
      }
}

现在你可以使用Moq来模拟IServiceIWantToCall,以便测试SomeMethod的逻辑。


2

补充pickles的答案,我为当前服务调用创建了一个名为IServiceinterface。然后我创建了一个继承该接口的ServiceMock类,并添加了一个名为_service的全局变量。在构造函数中,我实例化模拟服务并设置所有接口方法如下:

public class ServiceMock : IService
{
    Mock<IService> _serviceMock;

    public ServiceMock()
    {
        _serviceMock = new Mock<IService>();

        _serviceMock.Setup(x => x.GetString()).Returns("Default String");

        SomeClass someClass = new SomeClass();
        someClass.Property1= "Default";
        someClass.Property2= Guid.NewGuid().ToString();

        _serviceMock.Setup(x => x.GetSomeClass()).Returns(someClass);
    }

    public string GetString()
    {
        return _serviceMock.Object.GetString();
    }

    public License GetSomeClass()
    {
        return _serviceMock.Object.GetSomeClass();
    }
}

您可以将此类注入到代码中,而不是实际的Web服务。它将返回您设置它返回的值。现在,您可以进行测试而不依赖于Web服务。


1

首先,您必须能够注入Web服务。在SomeMethod()内创建新的Web服务会将该方法与生产代码“紧密耦合”;您不能动态地告诉它创建除mySoapClient以外的其他内容。

既然您想要创建和销毁它们,我可以建议您希望测试的代码接受 Func<IMySoapClient> 作为方法参数或构造函数参数。它看起来应该像这样:

public IMyInterface.SomeMethod(Func<IMySoapClient> clientFactory)
{    
     // hits a web service
     using ( mySoapClient client = clientFactory() )
     {
          var someResult = client.DoSomething();
       ...
       ...
     }
}

...或者:

public class MyClass:IMyInterface
{
   private Func<IMySoapClient> MySoapClientFactoryMethod;

   public MyClass(Func<IMySoapClient> clientFactoryMethod)
   {
      MySoapClientFactoryMethod = clientFactoryMethod;
   }

   ...

   public IMyInterface.SomeMethod()
   {    
        // hits a web service
        using ( mySoapClient client = MySoapClientFactoryMethod() )
        {
             var someResult = client.DoSomething();
          ...
          ...
        }
   }
}

现在,当您创建要测试的对象时,您定义一个函数来生成适当的Moq模拟Soap服务,该服务具有您从真实客户端期望的行为,但没有副作用(包括能够告诉代码Dispose()d客户端),并将该函数传递到您正在测试的类或方法中。在生产中,您可以简单地将函数定义为()=>new mySoapClient(),或者您可以设置一个IoC框架并注册mySoapClient作为IMySoapClient,然后还注册MyClass;大多数IoC框架都足够聪明,可以将委托视为参数并生成注入已注册依赖项的方法。

1
哦,你能告诉我这里 Func<> 的目的是什么吗?我对它不是很熟悉。 - O.O
Func<T> 是一个内置类型,它保存了一个委托(指向方法的“指针”),该委托不接受任何参数并返回泛型类型的对象(在您的情况下,您想要肥皂客户端)。请阅读有关 C# 中委托传递的内容;这是语言和框架非常强大的功能。 - KeithS
在单元测试中,我正在为单元测试模拟响应对象。在响应对象中,我想通过模拟为其中一个属性分配值。我该如何实现? - ravithejag
@ravithejag,我会通过在生成此响应对象的函数的模拟行为的定义中设置响应对象的属性来完成这个过程。 - KeithS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接