【问题标题】:How to mock web service call with Moq?如何使用 Moq 模拟 Web 服务调用?
【发布时间】:2012-08-30 19:42:30
【问题描述】:

下面的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....
}

【问题讨论】:

    标签: c# unit-testing moq


    【解决方案1】:

    您需要将 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 的答案,我为我当前的服务调用创建了一个 interface,名为 IService。然后我创建了一个继承接口的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 服务的情况下进行测试。

      【讨论】:

        【解决方案3】:

        您首先必须能够注入 Web 服务。在 SomeMethod() 中创建一个新方法将方法“紧密耦合”到生产代码;你不能动态地告诉它创建一个 mySoapClient 以外的东西。

        既然您想创建和销毁它们,我是否建议您要测试的代码接受Func&lt;IMySoapClient&gt; 作为方法参数或构造函数参数。它看起来像这样:

        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();
                  ...
                  ...
                }
           }
        }
        

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

        【讨论】:

        • 哦,你能告诉我 Func 的用途是什么吗?我不是很熟悉
        • Func&lt;T&gt; 是一种内置类型,它包含一个委托(指向方法的“指针”),它不接受任何参数并返回一个泛型类型的对象(在你的情况下,你想要肥皂客户端)。阅读 C# 中的委托传递;这是语言和框架的一个非常强大的特性。
        • 在单元测试中,我正在模拟单元测试的响应对象。在响应对象中,我想通过模拟将值分配给属性之一。我该如何实现这一点
        • @ravithejag 我会通过将响应对象的属性设置为生成此响应对象的函数的模拟行为定义的一部分来做到这一点。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-18
        • 1970-01-01
        • 2012-05-04
        • 1970-01-01
        • 2012-03-18
        • 2010-10-19
        相关资源
        最近更新 更多