8

我在 nUnit 中运行测试,通常我可以模拟依赖项,然后返回某些值或抛出错误。

我有一个作为内部 HttpClient 的类,我想测试这个类,我有什么选择。

这是我的代码,它不完整,以免淹没消息。如您所见,我在内部使用 HttpClient 而不是作为依赖项注入。该类引发了许多自定义异常,我想将这些最小起订量,否则我需要传递真实的用户名和密码,这将为我提供引发异常所需的状态代码。

有人有想法吗?如果我不能模拟 httpclient,那么我永远无法测试我的类是否会引发异常。

我真的必须将 HttpClient 更改为对构造函数的依赖吗?

public bool ItemsExist(string itemValue)
{

    var relativeUri = string.Format(UrlFormatString, itemValue.ToUpper());

    var uri = new Uri(new Uri(this.baseUrl), relativeUri);

    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", this.encodedCredentials);
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        var response = client.GetAsync(uri).Result;

        switch (response.StatusCode)
        {
            case HttpStatusCode.Unauthorized:
                // DO something here
                throw new CustomAuthorizationException();

            case HttpStatusCode.Forbidden:
                throw new CustomAuthenticationException();

        }

        return true;
4

3 回答 3

23

让我建议一个更简单的解决方案,不需要抽象/包装 httpclient,我相信它可以与模拟框架完美配合。

您需要为假 HttpMessageHandler 创建一个类,如下所示:

public class FakeHttpMessageHandler : HttpMessageHandler
{
    public virtual HttpResponseMessage Send(HttpRequestMessage request)
    {
        throw new NotImplementedException("Rember to setup this method with your mocking framework");
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        return Task.FromResult(Send(request));
    }
}

这样创建的HttpMessageHandler可以在实例化HttpClient时使用:

var msgHandler = new Mock<FakeHttpMessageHandler>() { CallBase = true };
var fakeclient = new HttpClient(msgHandler.Object);

您可以设置方法(此处使用 Moq):

msgHandler.Setup(t => t.Send(It.Is<HttpRequestMessage>(
            msg =>
                    msg.Method == HttpMethod.Post &&
                    msg.RequestUri.ToString() == "http://test.te/item/123")))
                    .Returns(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound));

您现在可以在必要时使用 fakeclient。

于 2014-08-14T10:54:27.257 回答
11

你不能像那样对它进行单元测试。就像您提到的那样: HttpClient 是一个依赖项,因此应该注入它。

就个人而言,我会创建自己的IHttpClient接口,由 实现HttpClientWrapper,它围绕System.Net.HttpClient. IHttpClient然后将作为依赖项传递给对象的构造函数。

如下,HttpClientWrapper不能进行单元测试。但是,我会编写几个集成测试来确保包装器编写得很好。

编辑

IHttpClient不必是HttpClient. 它只需要一个适合您需求的界面即可。它可以有尽可能多尽可能少的方法。

想象一下:HttpClient 允许你做很多事情。但是在你的项目中,你只是调用GetAsync(uri).Result方法,没有别的。

鉴于这种情况,您将编写以下接口和实现:

interface IHttpClient
{
    HttpResponseMessage Get(string uri);
}

class HttpClientWrapper : IHttpClient
{
    private readonly HttpClient _client;

    public HttpClientWrapper(HttpClient client)
    {
        _client = client;
    }


    public HttpResponseMessage Get(string uri)
    {
        return _client.GetAsync(new Uri(uri)).Result;
    }
}

因此,正如我之前所说,界面只需要满足您的需求即可。您不必环绕整个HttpClient类。

显然,你会像这样 moq 你的对象:

var clientMock = new Mock<IHttpClient>();
//setup mock
var myobj = new MyClass(clientMock.object);

并创建一个实际的对象:

var client = new HttpClientWrapper(new HttpClient());
var myobj = new MyClass(client );

编辑2

哦!而且别忘了IHttpClient还应该扩展IDisposable接口,很重要!

于 2013-09-10T16:04:33.977 回答
3

另一种选择是使用Flurl [披露:我是作者],一个用于构建和调用 URL 的库。它包括使伪造所有 HTTP 变得异常容易的测试助手。不需要包装器接口。

对于初学者,您的 HTTP 代码本身看起来像这样:

using Flurl;
using Flurl.Http;

...

try {
    var response = this.baseUrl
        .AppendPathSegment(relativeUri)
        .WithBasicAuth(username, password)
        .WithHeader("Accept", "application/json")
        .GetAsync().Result;

    return true;
}
catch (FlurlHttpException ex) {
    // Flurl throws on unsuccessful responses. Null-check ex.Response,
    // then do your switch on ex.Response.StatusCode.
}

现在为了测试乐趣:

using Flurl.Http.Testing;

...

[Test]
public void ItemsExists_SuccessResponse() {
    // kick Flurl into test mode - all HTTP calls will be faked and recorded
    using (var httpTest = new HttpTest()) {
        // arrange
        test.RespondWith(200, "{status:'ok'}");
        // act
        sut.ItemExists("blah");
        // assert
        test.ShouldHaveCalled("http://your-url/*");
    }
}

在 NuGet 上获取它:

PM> Install-Package Flurl.Http
于 2014-05-05T16:37:57.463 回答