如何为单元测试提供HttpWebResponse.GetResponseAsync()?

时间:2016-06-21 19:15:39

标签: c# unit-testing mocking httpwebrequest microsoft-fakes

我被分配为我无权修改的应用程序编写单元测试。我想进行单元测试的方法就是这样调用:

HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse;

我在这个项目中使用过Microsoft Fakes进行了数以千计的其他测试,所以我想我会在这里做同样的事情。在我看来,最简单,最干净的解决方案是使用request.GetResponseAsync()方法。然后我可以返回一些虚假内容并确保该方法正确处理它而不实际发出请求。

GetResponseAsync()会返回Task<WebResponse>个对象。所以我通常会这样做:

using (ShimsContext.Create())
{
    System.Net.Fakes.ShimWebRequest.AllInstances.GetResponseAsync = (x) =>
    {
        return new Task<WebResponse>(() =>
        {
            HttpWebResponse toRet = new HttpWebResponse();
            return toRet;
        });
    }
}

问题是上面的内容不能编译,因为

  

'System.Net.HttpWebResponse.HttpWebResponse()'已过时:'此API   支持.NET Framework基础结构,但并非如此   直接从你的代码中使用。'

我知道这种类型已经过时,我们现在应该使用不同的东西,但我试图测试的遗留代码不允许我这么奢侈。我已经看了很多关于这个主题的问题,但似乎没有人回答这个问题。

2 个答案:

答案 0 :(得分:7)

我将我的答案分成两部分;第一部分是你正在寻找的解决方案......在第二部分,我将讨论你在UT背景下的其他选择(所以这个答案会帮助其他人......)

由于您已经使用过MsFakes,因此可以使用Shims创建实例。 以下代码段是一个示例,显示了初始化和使用ShimHttpWebResponse的方法:

[Test]
public async Task InitializeShimHttpWebResponse()
{
    using (ShimsContext.Create())
    {
        ShimWebRequest.AllInstances.GetResponseAsync = (x) =>
        {
/* you can replace the var with WebResponse if you aren't going to set any behavior */
            var res = new ShimHttpWebResponse(); 
            return Task.FromResult((WebResponse)res);
        };

        ShimWebRequest.CreateString = uri =>
        {
            WebRequest req = new ShimFileWebRequest();
            return req;
        };

        var request = WebRequest.Create("");
        var response = await request.GetResponseAsync() as HttpWebResponse;

        Assert.IsNotNull(response);
    }
}

伪造配置:

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">
  <Assembly Name="System" Version="4.0.0.0"/>
  <ShimGeneration>
    <Add FullName="System.Net.HttpWebResponse"/>
    <Add FullName="System.Net.WebRequest"/>
    <Add FullName="System.Net.HttpWebRequest"/>
    <Add FullName="System.Net.FileWebRequest"/>    
  </ShimGeneration>
</Fakes>

总结一下这一部分; IMO,对于通用案例,使用代码编织工具(MsFakes)是处理这种情况的正确方法。(我将在下一节中进一步解释)

正如我所见,您有4个选项来创建HttpWebResponse的新实例:

1。使用反射 - 在这种情况下一个坏主意(UT ..)

2. 继承 - 自定义模拟...

3. 使用基于代理的框架例如; Moq,Rhinomocks等。

4. 使用代码编织工具(正如您已经使用过的那样......)例如; Msfakes,Typemock Isolator等。

还有一个选项:创建集成测试而不是UT ...

<强> 1。反射

HttpWebResponse有3个C&#39; {publicinternalprotected

要使用内部\公共C&#39;您必须使用反射,但在大多数情况下,它不会开箱即用,那么您将不得不违反一些UT规则(小,快等等......)

反射将开箱即用的2种情况是:

  1. 您不会在实例上调用任何有问题的属性/方法,而SUT(正在测试的主题)只是将此实例传递给其中一个依赖项

  2. 您将使用更多反射来初始化实例字段。

  3. 虽然第一个是一个简单的情况(如果这是你的情况,那么你应该使用反射),第二个是在UT的背景下的坏习惯;你的UT不会小/可读/可维护,执行时间会增加,微软可能会进行一些重构,这可能会破坏你的UT。

    对于受保护的那个,你应该通过继承来调用它(编译与运行时...)。

    <强> 2。继承:

    受保护的C&C还具有过时的属性,但属性IsError设置为false,这允许您继承此类,然后您将能够更改方法的行为/性能;虚拟 - 覆盖,非虚拟 - 仅当您有权访问类成员(或反射)

    此选项的主要缺点是您必须生成的代码量及其复杂性。

    第3。使用基于代理的框架:

    在场景后面,这些工具使用反射来创建基于类的继承实例(组合选项1和2) 这些工具有一些内置的方法可以使您的假代码更小/可读/可维护。

    缺点:(我不会指出所有基于代理的工具的缺点......)

    1. 您仍然无法改变非虚拟方法的行为。

    2. 您无法访问实例成员。

    3. 可以使用代码编织工具解决这两个问题。

      <强> 4。代码编织工具:

      这些工具可以让你做几乎任何事情,这就是为什么这些工具最适合一般情况的原因(我可以用句子总结主要的缺点 - 强大的力量带来了很大的责任......)。 在您的情况下,他们为您提供最佳解决方案; 由于HttpWebResponse具有非虚拟方法,并且您不想重构代码,因此这是您的正确解决方案。

      然而,Msfakes并没有为你提供UT(AssertsWasCalled,计数等等)的额外方法/功能,所以除非你打算用其他工具替换这个工具,你应该IMO将它与代理基础工具(免费工具!!!!)结合起来

答案 1 :(得分:1)

你的问题与Shim无关。使用反射来绕过过时的问题。

HttpWebResponse toRet = Activator.CreateInstance<HttpWebResponse>();