单元测试Windows.Web.Http HttpClient与模拟IHttpFilter和IHttpContent,

本文关键字:IHttpFilter IHttpContent 模拟 Windows Web Http HttpClient 单元测试 | 更新日期: 2023-09-27 18:14:56

我有一个类,依赖于来自Windows. web . http (Windows 10 UAP应用程序)的HttpClient。我想进行单元测试,因此我需要"模拟"HttpClient来设置Get-Call应该返回什么。我从使用手写模拟的IHttpFilter和IHttpContent的HttpClient进行"简单"单元测试开始。它没有像预期的那样工作,我在Test-Explorer中得到InvalidCastException。

单元测试看起来像:

    [TestMethod]
    public async Task TestMockedHttpFilter()
    {
        MockedHttpContent mockedContent = new MockedHttpContent("Content from MockedHttpContent");
        MockedHttpFilter mockedHttpFilter = new MockedHttpFilter(HttpStatusCode.Ok, mockedContent);
        HttpClient httpClient = new HttpClient(mockedHttpFilter);
        var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask().ConfigureAwait(false);
        // Test stops here, throwing System.InvalidCastException: Specified cast is not valid
        // Code not reached...
        var result = await resultContentTask.Content.ReadAsStringAsync();
        Assert.AreEqual("Content from MockedHttpContent", result);
    }

我实现了IHttpFilter在MockedHttpFilter:

public class MockedHttpFilter : IHttpFilter
{
    private HttpStatusCode _statusCode;
    private IHttpContent _content;
    public MockedHttpFilter(HttpStatusCode statusCode, IHttpContent content)
    {
        _statusCode = statusCode;
        _content = content;
    }
    public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
    {
        return AsyncInfo.Run<HttpResponseMessage, HttpProgress>((token, progress) =>
        Task.Run<HttpResponseMessage>(()=>
        {
            HttpResponseMessage response = new HttpResponseMessage(_statusCode);
            response.Content = _content;
            return response; // Exception thrown after return, but not catched by code/debugger...
        }));
    }
}

我实现了IHttpContent在MockedHttpContent:

public class MockedHttpContent : IHttpContent
{
    private string _contentToReturn;
    public MockedHttpContent(string contentToReturn)
    {
        _contentToReturn = contentToReturn;
    }
    public HttpContentHeaderCollection Headers
    {
        get
        {
            return new HttpContentHeaderCollection();
        }
    }
    public IAsyncOperationWithProgress<string, ulong> ReadAsStringAsync()
    {
        return AsyncInfo.Run<string, ulong>((token, progress) => Task.Run<string>(() =>
        {
            return _contentToReturn;
        }));
    }
}

Test-Explorer结果视图中的错误:

Test Name:  TestMockedHttpFilter
Test FullName:  xxx.UnitTests.xxxHttpClientUnitTests.TestMockedHttpFilter
Test Source:    xxx.UnitTests'xxxHttpClientUnitTests.cs : line 22
Test Outcome:   Failed
Test Duration:  0:00:00.1990313
Result StackTrace:  
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at xxx.UnitTests.xxxHttpClientUnitTests.<TestMockedHttpFilter>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Result Message: Test method xxx.UnitTests.xxxHttpClientUnitTests.TestMockedHttpFilter threw exception: 
System.InvalidCastException: Specified cast is not valid.

首先,不确定为什么抛出异常/我做错了什么。也许有人能给我指出正确的方向,或者给我一个提示,下一步要检查/测试什么?

第二,有没有更好的方法来单元测试代码与HttpClient依赖(Windows 10 UAP)?

单元测试Windows.Web.Http HttpClient与模拟IHttpFilter和IHttpContent,

你的单元测试没有意义,你并没有真正测试你的类,而是试图测试HttpClient类。我要做一些假设因为你的问题中没有测试的方法;我假设您做了如下操作:

public async Task<MyCustomClass> MethodUnderTest()
{
    // ...
    using (var client = new HttpClient(...))
    {
        // ...
        var json = response.Content.ReadAsStringAsync();
        return JsonConvert.Deserialize<MyCustomClass>(json);
    }
}

如果是这种情况,那么你必须明白你的类不接受某些依赖。你可以修改类,让每个外部依赖项都被注入,但这可能有点小题大做……您真的希望任何消费和实例化您的类的人都必须向您提供HttpClient吗?所以,更好的选择是使用一个mock框架,它可以模拟一个具体类的依赖;大多数mock框架可以处理mock接口(这些很容易),但是很少有框架可以处理mock具体类。

我建议使用的框架是Microsoft Fakes框架。Microsoft Fakes支持Shims(隔离)和stub(模拟)。隔离是为了控制具体类的成员调用所需要的。 那么,在我的例子中,哪些成员需要被控制呢?
  1. HttpResponseMessage。Content_Get
  2. HttpContent。ReadAsStringAsync

我不认为你需要改变JsonConvert.Deserialize<T>()成员的行为,但是如果你想的话,你可以这样做。Microsoft Fakes框架一开始有点令人生畏,但一旦你开始使用它并让它工作,它就会变得更容易使用。或者,您可以使用其他支持隔离的框架:

  • JustMock from Telerik
  • TypeMock隔离器

似乎您没有使用AsTask过载,这与httpClient.SendRequestAsync(...)的返回值相匹配

尝试更改

var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask().ConfigureAwait(false);

var resultContentTask = await httpClient.SendRequestAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://dontcare.ch"))).AsTask<IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress>>().ConfigureAwait(false);

最简单的解决方案是使用这个库来模拟HttpClient

https://github.com/richardszalay/mockhttp

PM> Install-Package RichardSzalay.MockHttp

我也测试了它与您附加的代码

       //Arrange
        var mockHttp = new MockHttpMessageHandler();
        mockHttp.When("http://dontcare.ch")
            .Respond("application/txt", "Content from MockedHttpContent"); // Respond with content
        var client = mockHttp.ToHttpClient();
        //Act
        var response = await client.GetAsync("http://dontcare.ch");
        var result = await response.Content.ReadAsStringAsync();
        //Assert
        Assert.AreEqual("Content from MockedHttpContent", result);