存根或模拟 ASP.NET Web API HttpClient

Posted

技术标签:

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

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

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

【问题讨论】:

【参考方案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)注入到您的服务类中,它会按照配置进行响应。

下面显示基本用法。 WhenRespond 方法有很多重载,包括运行自定义逻辑。 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

以上是关于存根或模拟 ASP.NET Web API HttpClient的主要内容,如果未能解决你的问题,请参考以下文章

如何保护 ASP.NET Core Web API 免受被盗 JWT 令牌以进行模拟

ASP.NET Core Web API 应用程序是不是可能出现死锁或应用程序挂起状态

谷歌日历订阅 ASP.NET

无需 api 或 web 服务调用即可集成 asp.net 服务

使用 ASP.NET 和 TFS api 的直通(模拟)身份验证

JQuery + ASP.Net Web API一个简单应用