测试时,HttpClient.SendAsync不使用DelegatingHandler

时间:2017-10-24 17:43:26

标签: c# httpclient delegatinghandler

我们的C#项目中有几个类可以调用第三方API。我们正在使用HttpClient对象进行调用。我们已经设置了我们的类,我们进行这些调用以接受HttpClient,以便在测试时,我们可以使用自定义/假的DelegatingHandler与客户端。

我们已经设置了这样的类:

public class CallingService : ApiService
{
    private readonly ISomeOtherService _someOtherService;

    public CallingService (ILogger logger, 
        IConfigurationManager configurationManager,
        ISomeOtherService someOtherService) : base(logger, configurationManager)
    {
        _someOtherService = someOtherService;
    }

    public CallingService (ILogger logger,
        HttpClient client,
        IConfigurationManager configurationManager,
        ISomeOtherService someOtherService) : base(logger, configurationManager, client)
    {
        _someOtherService = someOtherService;
    }

    private async Task<XmlNodeList> TransmitToApi(string xml_string)
    {
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12;
        //..
        string type = "application/xml";
        var content = new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes(xml_string)));
        var targetUri = new Uri(ConfigurationManager.GetAppSetting("ApiUrl"));
        var message = new HttpRequestMessage
        {
            RequestUri = targetUri ,
            Method = HttpMethod.Post,
            Content = content
        };

        message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
        message.Content.Headers.Add("Content-Type", type);
        message.Headers.Add("someHeader", someData);

        HttpResponseMessage response = null;
        try
        {
            // Define the cancellation token.
            CancellationTokenSource source = new CancellationTokenSource();
            CancellationToken token = source.Token;
            response = await Client.SendAsync(message, token);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        //...
        return someData;
    }

基类ApiService类定义了一个通用的HttpClient对象(如果没有提供)。

我们目前正在使用SendAsync,因此我们可以定义邮件标头。 (我们有比此处列出的标题更多的标题。)

测试定义了DelegatingHandler,如下所示:

public class FakeResponseHandler : DelegatingHandler
{
    private readonly Dictionary<Uri, HttpResponseMessage> _fakeResponses = new Dictionary<Uri, HttpResponseMessage>();

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage, string content = "", bool asXml = false)
    {
        if (!string.IsNullOrWhiteSpace(content))
        {
            if (asXml)
            {
                responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/xml");
            }
            else
            {
                responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
            }
        }
        _fakeResponses.Add(uri, responseMessage);
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (_fakeResponses.ContainsKey(request.RequestUri))
        {
            return _fakeResponses[request.RequestUri];
        }
        return new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request };
    }
}

然后:

[Fact]
public async Task ItWillDoStuffAndCallApi()
{
    using (var mock = AutoMock.GetLoose())
    {
        mock.Mock<IConfigurationManager>()
            .Setup(cm => cm.GetAppSetting("ApiUrl"))
            .Returns("http://example.org/test/");

        string testReturnData = GetFileContents("IntegrationTests.SampleData.SampleApiResponseXML.txt");
        FakeResponseHandler fakeResponseHandler = new FakeResponseHandler();
        fakeResponseHandler.AddFakeResponse(new Uri("http://example.org/test/"),
            new HttpResponseMessage(HttpStatusCode.OK),
            testReturnData,
            true);

        //HttpClient httpClient = new HttpClient(fakeResponseHandler);
        HttpClient httpClient = HttpClientFactory.Create(fakeResponseHandler);
        mock.Provide(httpClient);

        var ourService = new CallingService();
        ourService.TransmitToApi(someXmlString);
    }
}

当我们运行测试时,收到消息:

  

处理程序未返回响应消息。

我们似乎永远不会进入DelegatingHandler.SendAsync方法。

我们还有其他类使用HttpClient.PostAsyncGetAsync来调用API,这些类会调用DelegatingHandler.SendAsync方法并按预期工作。

我们尝试过:

HttpClient httpClient = new HttpClient(fakeResponseHandler);


    HttpClient httpClient = HttpClientFactory.Create(fakeResponseHandler);

我们还尝试使用和不使用取消令牌Client.SendAsync

为什么这不起作用?

我们是否应该重新编写此内容以使用PostAsync

1 个答案:

答案 0 :(得分:0)

我需要查看HttpClientFactory.Create的实现以及Client.SendAsync内部实际执行的操作但是我能够使用您提供的示例代码并填写我可以执行的空白得到以下工作:

public class FakeResponseHandler : DelegatingHandler
{
    private readonly Dictionary<Uri, HttpResponseMessage> _fakeResponses = new Dictionary<Uri, HttpResponseMessage>();

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage, string content = "", bool asXml = false)
    {
        if (!string.IsNullOrWhiteSpace(content))
        {
            if (asXml)
            {
                responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/xml");
            }
            else
            {
                responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
            }
        }
        _fakeResponses.Add(uri, responseMessage);
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var emptyContent = string.Empty;

        if (request.Content.Headers.ContentType.MediaType == "application/xml")
            emptyContent = "<empty />";

        return Task.FromResult(_fakeResponses.ContainsKey(request.RequestUri) ?
            _fakeResponses[request.RequestUri] :
            new HttpResponseMessage(HttpStatusCode.NotFound)
            {
                RequestMessage = request,
                Content = new StringContent(emptyContent)
            });
    }
}

只是为了使事情干净,使用Task.FromResult返回SendAsync中的任务,并提供空内容以避免空引用异常。

public class CallingService
{
    private readonly HttpClient _httpClient;
    private readonly IConfigurationManager _configurationManager;

    public CallingService(HttpClient httpClient, 
        IConfigurationManager configurationManager)
    {
        _httpClient = httpClient;
        _configurationManager = configurationManager;
    }

    public async Task<XmlNodeList> TransmitToApi(string xml_string)
    {
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12;
        //..
        string type = "application/xml";
        var content = new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes(xml_string)));
        var targetUri = new Uri(_configurationManager.GetAppSetting("ApiUrl"));
        var message = new HttpRequestMessage
        {
            RequestUri = targetUri,
            Method = HttpMethod.Post,
            Content = content
        };

        message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
        message.Content.Headers.Add("Content-Type", type);

        string somedata;
        try
        {
            // Define the cancellation token.
            CancellationTokenSource source = new CancellationTokenSource();
            CancellationToken token = source.Token;
            var response = await _httpClient.SendAsync(message, token);

            somedata = await response.Content.ReadAsStringAsync();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        //...
        var xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(somedata);
        return xmlDoc.SelectNodes("*");
    }
}

然后测试将HttpClient的实例传递给CallingService

    [TestMethod]
    public async Task TestMethod1()
    {
        const string content = @"<root><test>1243</test></root>";
        const string httpExample = "http://example.org/test/";

        var configurationManager = new Mock<IConfigurationManager>();

        configurationManager
            .Setup(cm => cm.GetAppSetting("ApiUrl"))
            .Returns(httpExample);

        var fakeResponseHandler = new FakeResponseHandler();
        fakeResponseHandler.AddFakeResponse(new Uri(httpExample),
            new HttpResponseMessage(HttpStatusCode.OK), content, true);

        using (var httpClient = new HttpClient(fakeResponseHandler))
        {
            var ourService = new CallingService(httpClient, configurationManager.Object);

            var result = await ourService.TransmitToApi(content);

            Assert.AreEqual(content, result.Item(0)?.OuterXml);
        }
    }

这一切都有效,如果我不得不猜测 - 问题将出现在HttpClientFacotry的某个地方。

希望有所帮助!!干杯,:))