使用 httpclient 调用 Web API 的单元测试 MVC 操作

Unit test MVC action calling Web API using httpclient

请看下面的代码。使用 httpclient 的内存托管,并将 httpclient 对象传递给控制器​​,以便对操作方法进行单元测试。但是我在 HttpResponseMessage 响应 =_httpClient.GetAsync 后得到 "Internal Server Error" ReasonPhrase。请帮助我,这是正确的做法吗?

    private readonly HttpClient _httpClient;
    public SecurityMfMvcController(HttpClient httpClient)
    {
        this._httpClient = httpClient;
    }
    [HttpGet]
    public ActionResult GetSecuritiesMfs()
    {
        try
        {                
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response =
                _httpClient.GetAsync(ConfigurationManager.AppSettings["ApiUrl"] + "SecuritiesWebApiMf").Result;
            response.EnsureSuccessStatusCode();
            List<SecurityMutualFundDto> list =
            response.Content.ReadAsAsync<List<SecurityMutualFundDto>>().Result;
            return View("SecuritiesMf", list);

        }
        catch (Exception ex)
        {
            return View("Error", ex.Message);
        }

    }
//Unit Test Method for this Action
 [Test]
    public void TestActionGetSecuritiesMfs()
    {
        var config = new HttpConfiguration()
        {
            IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always
        };
        //use the configuration that the web application has defined
        WebApiConfig.Register(config);
        HttpServer server = new HttpServer(config);
        //create a client with a handler which makes sure to exercise the formatters
        using (var client = new HttpClient(new InMemoryHttpContentSerializationHandler(server)))
        {
            System.Uri uri = new System.Uri("http://localhost:55893/api/");
            client.BaseAddress = uri;
            var controller = new SecurityMfMvcController(client);
            var result = controller.GetSecuritiesMfs();
            Assert.IsNotNull(result);
        }

    }
//MessageHandler
public class InMemoryHttpContentSerializationHandler : DelegatingHandler
{
    public InMemoryHttpContentSerializationHandler(HttpMessageHandler innerHandler)
        : base(innerHandler)
    {
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Content = await ConvertToStreamContentAsync(request.Content);

        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        response.Content = await ConvertToStreamContentAsync(response.Content);

        return response;
    }

    private async Task<StreamContent> ConvertToStreamContentAsync(HttpContent originalContent)
    {
        if (originalContent == null)
        {
            return null;
        }

        StreamContent streamContent = originalContent as StreamContent;

        if (streamContent != null)
        {
            return streamContent;
        }

        MemoryStream ms = new MemoryStream();

        await originalContent.CopyToAsync(ms);

        // Reset the stream position back to 0 as in the previous CopyToAsync() call,
        // a formatter for example, could have made the position to be at the end
        ms.Position = 0;

        streamContent = new StreamContent(ms);

        // copy headers from the original content
        foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
        {
            streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        return streamContent;
    }
}

您可以模拟您的 http 请求管道并测试您的操作:

  var mockHttpRequest = new Mock<HttpRequestMessage>(new object[] {new HttpMethod("GET"), "www.someuri.com"});
  var mockHttpConfig = new Mock<HttpConfiguration>();
  var mockRouteData = new Mock<IHttpRouteData>();

  var mockHttpContext =
    new Mock<HttpControllerContext>(new object[]
                                      {mockHttpConfig.Object, mockRouteData.Object, mockHttpRequest.Object});

然后使用这些值设置您的控制器对象:

var controller = new YourController();
controller.ControllerContext = mockHttpContext.Object;
controller.Request = controller.ControllerContext.Request;
response = controller.SecuritiesMF();

您可以按如下方式检查您的回复:

Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);

我搞定了,如有不妥请指正。我必须创建一个如下所示的 "FakeHttpMessageHandler" 和匹配 System.Net.Http.StreamContent 的内容类型 application/json 内容类型。下面的代码正在使用 httpclient 调用 WebAPI 对 mvc 操作方法进行单元测试。但是我需要仔细检查这是否是单元测试的正确方法,将进一步审查。

[Test]
    public void TestActionMethodSelectByIdUsingFakeHandler()
    {
        var uobj = new UnitTestForApiController();
        var testobj= uobj.GetsecuritiesMfsList();
        MemoryStream stream = new MemoryStream();
        IFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, testobj);
        var response = new HttpResponseMessage(HttpStatusCode.OK) 
        {Content = new StreamContent(stream)};

        using (var httpClient = new HttpClient(new FakeHandler
        {
            Response = response,
            InnerHandler = new HttpClientHandler()
        }))
        {
            System.Uri uri = new System.Uri("http://localhost:55893/api/");
            httpClient.BaseAddress = uri;
            var controller = new SecuritiesMfMvcController(httpClient);
            var result = controller.Select(2155) as ViewResult;
            Assert.IsNotNull(result);
            Assert.AreEqual(result.ViewName,"Select");
            Assert.AreEqual(result.Model, testobj.FirstOrDefault());

        }

//FakeHandler class 如下

public class FakeHandler : DelegatingHandler
{
    public HttpResponseMessage Response { get; set; }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => Response, cancellationToken);
    }
}

//We can also have logic for abstracting appropriate StreamContent Creation into FakeContent class like below:
public class FakeHttpContent : HttpContent
{
    public object Content { get; set; }

    public FakeHttpContent(object content)
    {
        Content = content;
    }

    protected async override Task SerializeToStreamAsync(Stream stream,
        TransportContext context)
    {
        MemoryStream ms = new MemoryStream();
        IFormatter formatter = new BinaryFormatter();
        formatter.Serialize(ms, Content);
        await ms.CopyToAsync(stream);
    }

    protected override bool TryComputeLength(out long length)
    {
        length = Content.ToString().Length;
        return true;
    }
}