使用 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;
}
}
请看下面的代码。使用 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;
}
}