【问题标题】:Stubbing or Mocking ASP.NET Web API HttpClient存根或模拟 ASP.NET Web API HttpClient
【发布时间】:2012-05-21 23:30:55
【问题描述】:

我在一个项目中使用新的 Web API 位,我发现我不能使用普通的HttpMessageRequest,因为我需要在请求中添加客户端证书。结果,我使用了HttpClient(所以我可以使用WebRequestHandler)。这一切都很好,除了它不是存根/模拟友好,至少对于犀牛模拟。

我通常会在 HttpClient 周围创建一个包装服务,我会使用它来代替,但如果可能的话,我想避免这种情况,因为我需要包装很多方法。我希望我遗漏了一些东西——关于如何存根 HttpClient 的任何建议?

【问题讨论】:

    标签: c# unit-testing dotnet-httpclient rhino-mocks


    【解决方案1】:

    作为@Raj 已经提出的优秀想法的替代方案,可以降低一步并模拟/伪造HttpMessageHandler。

    如果你让任何需要HttpClient 的类在构造函数中接受它作为依赖注入参数,那么在单元测试时你可以传入一个HttpClient,这个HttpClient 已经注入了你自己的HttpMessageHandler。这个简单的类只有一个抽象方法需要你实现,如下:

    public class FakeHttpMessageHandler : HttpMessageHandler
        {
        public HttpRequestMessage RequestMessage { get; private set; }
    
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
            RequestMessage = request;
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
            }
        }
    

    我的简单示例只是将 HttpRequestMessage 保存在公共属性中以供以后检查并返回 HTTP 200(OK),但您可以通过添加一个设置您想要返回的结果的构造函数来扩充它。

    你会像这样使用这个类:

    public void foo()
        {
        //Arrange
        var fakeHandler = new FakeHttpMessageHandler();
        var client = new HttpClient(fakeHandler);
        var SUT = new ClassUnderTest(client);
    
        //Act
        SUT.DomSomething();
    
        //Assert
        fakeHandler.RequestMessage.Method.ShouldEqual(HttpMethod.Get); // etc...
        }
    

    这种方法存在局限性,例如在发出多个请求或需要创建多个HttpClients 的方法中,假处理程序可能开始变得过于复杂。但是,对于简单的情况可能值得考虑。

    【讨论】:

    • 虽然这是一个巧妙的想法,但这种方法会创建非常脆弱的测试。 HttpClient 实现将来可能会发生变化。你让测试依赖于类的内部工作。包装器恕我直言仍然是最好的方法。
    • 这是一个公共构造函数参数。我不认为它会改变。我非常喜欢这个答案。
    • 太棒了,应该被接受的答案。我同意@Kugel 的观点——这不太可能改变,而且我愿意承担风险。
    • @PanagiotisKanavos:建议的解决方案不是“单元”测试,因为它不仅测试被测类,还取决于 HttpClient 的实现。将来是否更改,这不是“单元测试”。
    • @Kai Eichinger 我的“奇怪的缩进”对你来说可能看起来很奇怪,但自 1981 年我在大学学习编译器编写技术并了解到大括号实际上是它们包围的块,而不是外部块。您可以使用 LINQPad 5 进行验证并查看语法树 缩进大括号(有人称之为“Whitesmith 风格”)是我组织的最佳实践。所以请不要改变我的缩进,除非它在语法上是错误的,假设你的缩进偏好应该覆盖原始海报的,这是傲慢的。
    【解决方案2】:

    几个月前我发布了一个名为MockHttp 的库,它可能很有用。它使用一个自定义的HttpMessageHandler 和一个流畅的(和可扩展的)API。您可以将模拟处理程序(或 HttpClient)注入到您的服务类中,它会按照配置进行响应。

    下面显示基本用法。 When 和 Respond 方法有很多重载,包括运行自定义逻辑。 documentation on the GitHub page 包含更多细节。

    var mockHttp = new MockHttpMessageHandler();
    
    // Setup a respond for the user api (including a wildcard in the URL)
    mockHttp.When("http://localhost/api/user/*")
            .Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON
    
    // Inject the handler or client into your application code
    var client = new HttpClient(mockHttp);
    
    var response = async client.GetAsync("http://localhost/api/user/1234");
    // or without async: var response = client.GetAsync(...).Result;
    
    var json = await response.Content.ReadAsStringAsync();
    
    // No network connection required
    Console.Write(json); // {'name' : 'Test McGee'}
    

    【讨论】:

    • 真的很喜欢这个库,和$httpBackend的相似之处。我采用了这种方法,效果很好。
    • 谢谢詹姆斯,很高兴你喜欢它!
    • 我们显然是按照同样的思路思考的。我的用例非常简单,没有理由走这么远,但我想如果你在这方面做了很多工作,那么这样的库将是一个真正的节省时间。下次我需要这样的东西时,我会为这个项目添加书签。
    • 这是一个很棒的图书馆人。我真的很喜欢它并且已经在我们的项目中使用它。
    【解决方案3】:

    我使用最小起订量,我可以删除 HttpClient。我认为 Rhino Mock 也是如此(我自己没有尝试过)。 如果你只是想存根 HttpClient 下面的代码应该可以工作:

    var stubHttpClient = new Mock<HttpClient>();
    ValuesController controller = new ValuesController(stubHttpClient.Object);
    

    如果我错了,请纠正我。我猜你在这里指的是在 HttpClient 中删除成员。

    大多数流行的隔离/模拟对象框架不允许您在非虚拟成员上存根/设置 例如下面的代码抛出异常

    stubHttpClient.Setup(x => x.BaseAddress).Returns(new Uri("some_uri");
    

    您还提到您希望避免创建包装器,因为您会包装很多 HttpClient 成员。不清楚为什么需要包装很多方法,但您可以轻松地只包装您需要的方法。

    例如:

    public interface IHttpClientWrapper  {   Uri BaseAddress { get;  }     }
    
    public class HttpClientWrapper : IHttpClientWrapper
    {
       readonly HttpClient client;
    
       public HttpClientWrapper()   {
           client = new HttpClient();
       }
    
       public Uri BaseAddress   {
           get
           {
               return client.BaseAddress;
           }
       }
    }
    

    我认为可能对您有用的其他选项(那里有很多示例,所以我不会编写代码) 微软鼹鼠框架 http://research.microsoft.com/en-us/projects/moles/ Microsoft Fakes:(如果您使用的是 VS2012 Ultimate) http://msdn.microsoft.com/en-us/library/hh549175.aspx

    【讨论】:

    • 感谢您的回答。我最终选择了包装方法。如果我必须再做一次,我可能会使用 TypeMock 之类的东西,或者 MS 解决方案之一。
    • 我最初为密封类中的特定事物编写了具体实现,并在内部紧密耦合.. 但我现在意识到要让测试与 Moq 很好地配合使用,正如你提到的,我必须解耦类以获得更好的测试覆盖率。我知道我必须这样做,我只需要搜索它,然后说服自己不要偷懒:D
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-19
    • 1970-01-01
    • 2019-03-21
    • 2011-12-17
    • 1970-01-01
    • 2013-03-25
    相关资源
    最近更新 更多