重构代码以允许对HttpClient进行单元测试 [英] Refactoring code to allow for unit testing of HttpClient

查看:194
本文介绍了重构代码以允许对HttpClient进行单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在处理一段看起来像这样的代码:

I am dealing with a piece of code that looks like this:

public class Uploader : IUploader
{
    public Uploader()
    {
        // assign member variables to dependency injected interface implementations
    }

    public async Task<string> Upload(string url, string data)
    {
        HttpResponseMessage result;
        try
        {
            var handler = new HttpClientHandler();
            var client = new HttpClient(handler);

            result = await client.PostAsync(url, new FormUrlEncodedContent(data));

            if (result.StatusCode != HttpStatusCode.OK)
            {
                return "Some Error Message";
            }
            else
            {
                return null; // Success!
            }
        }
        catch (Exception ex)
        {
            // do some fancy stuff here
        }
    }
}

我正在尝试对上传进行单元测试功能。特别是,我需要模拟 HttpClient 。在阅读了此处和这些 两个我知道,解决此问题的更好方法之一是模拟 HttpMessageHandler 并将其传递给 HttpClient 并让它返回我想要的内容。

I am trying to unit test the Upload function. In particular, I need to mock the HttpClient. After reading the other answers on here and these two articles, I know that one of the better ways to solve this is to mock the HttpMessageHandler instead and pass that to HttpClient and have it return whatever I want.

因此,我首先通过在 HttpClient 中传入构造函数作为依赖项:

So, I started along that path by first passing in HttpClient in the constructor as a dependency:

public class Uploader : IUploader
{
    private readonly HttpClient m_httpClient; // made this a member variable

    public Uploader(HttpClient httpClient) // dependency inject this
    {
        m_httpClient = httpClient;
    }

    public async Task<string> Upload(string url, string data)
    {
        HttpResponseMessage result;
        try
        {
            var handler = new HttpClientHandler();

            result = await m_httpClient.PostAsync(url, new FormUrlEncodedContent(data));

            if (result.StatusCode != HttpStatusCode.OK)
            {
                return "Some Error Message";
            }
            else
            {
                return null; // Success!
            }
        }
        catch (Exception ex)
        {
            // do some fancy stuff here
        }
    }
}

并添加: services.AddSingleton< HttpClient>(); Startup.cs ConfigureServices 方法。

但是现在我面临一个小问题,即原始代码专门创建了一个 HttpClientHandler 来传递。然后我如何重构它以采用可模拟的处理程序?

But now I face a slight issue where the original code specifically creates a HttpClientHandler to pass in. How then do I refactor that to take in a mockable handler?

推荐答案

我发现最简单的方法是继续使用 HttpClient ,但是传递模拟 HttpClientHandler ,例如 https:// github。 com / richardszalay / mockhttp

I find the simplest way is to continue using HttpClient, but pass in a mocking HttpClientHandler such as https://github.com/richardszalay/mockhttp

上面链接中的代码示例:

Code sample from the link above:

var mockHttp = new MockHttpMessageHandler();

mockHttp.When("http://localhost/api/user/*")
        .Respond("application/json", "{'name' : 'Test McGee'}");

// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();

var response = await client.GetAsync("http://localhost/api/user/1234");

var json = await response.Content.ReadAsStringAsync();

Console.Write(json); // {'name' : 'Test McGee'}

.NET Core中内置的依赖注入框架忽略内部构造函数,因此在这种情况下它将调用无参数构造函数。

The Dependency Injection framework built into .NET Core ignores internal constructors, so it will call the parameter-less constructor in this scenario.

public sealed class Uploader : IUploader
{
    private readonly HttpClient m_httpClient;

    public Uploader() : this(new HttpClientHandler())
    {
    }

    internal Uploader(HttpClientHandler handler)
    {
        m_httpClient = new HttpClient(handler);
    }

    // regular methods
}

在单元测试中,可以使用接受 HttpClientHandler 的构造函数:

In your unit tests, you can use the constructor accepting the HttpClientHandler:

[Fact]
public async Task ShouldDoSomethingAsync()
{
    var mockHttp = new MockHttpMessageHandler();

    mockHttp.When("http://myserver.com/upload")
        .Respond("application/json", "{'status' : 'Success'}");

    var uploader = new Uploader(mockHttp);

    var result = await uploader.UploadAsync();

    Assert.Equal("Success", result.Status);
}

通常我不喜欢使用内部构造函数来方便测试,但是,与注册共享的 HttpClient 相比,我发现这更明显且自成体系。

Normally I'm not a big fan of having an internal constructor to facilitate testing, however, I find this more obvious and self-contained than registering a shared HttpClient.

HttpClientFactory 可能是另一个不错的选择,但是我还没做太多,所以我只提供自己认为有用的信息。

HttpClientFactory might be another good option, but I haven't played around with that too much, so I'll just give info on what I've found useful myself.

这篇关于重构代码以允许对HttpClient进行单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆