我有一些问题,试图包装我的代码在单元测试中使用。问题在于。我有接口IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
使用它的类HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
然后是Connection类,它使用simpleIOC注入客户端实现:
public class Connection
{
private IHttpHandler _httpClient;
public Connection(IHttpHandler httpClient)
{
_httpClient = httpClient;
}
}
然后我有一个单元测试项目,它有这个类:
private IHttpHandler _httpClient;
[TestMethod]
public void TestMockConnection()
{
var client = new Connection(_httpClient);
client.doSomething();
// Here I want to somehow create a mock instance of the http client
// Instead of the real one. How Should I approach this?
}
现在很明显,我将在Connection类中拥有从后端检索数据(JSON)的方法。但是,我想为这个类编写单元测试,显然我不想针对真正的后端编写测试,而是一个模拟的后端。我试着给这个问题一个好的答案,但没有成功。我以前可以用Moq来模拟,但从来没有在HttpClient这样的东西上使用过。我应该如何处理这个问题?
如果您不介意运行自己的http服务器,可以尝试Xim。其实很简单:
using Xim.Simulators.Api;
[Test]
public async Task TestHttpGetMethod()
{
using var simulation = Simulation.Create();
using var api = simulation
.AddApi()
.AddHandler("GET /books/1234", ApiResponse.Ok())
.Build();
await api.StartAsync();
var httpClient = new HttpClient();
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"{api.Location}/books/1234"));
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.IsTrue(api.ReceivedApiCalls.Any(call => call.Action == "GET /books/1234"));
}
这是使用模拟的一个很好的替代方案,可能适合您在某些场景中的需求。它建立在Kestrel的基础上(是的,我是作者)。
我做了一些非常简单的事情,因为我在一个依赖注入环境中。
public class HttpHelper : IHttpHelper
{
private ILogHelper _logHelper;
public HttpHelper(ILogHelper logHelper)
{
_logHelper = logHelper;
}
public virtual async Task<HttpResponseMessage> GetAsync(string uri, Dictionary<string, string> headers = null)
{
HttpResponseMessage response;
using (var client = new HttpClient())
{
if (headers != null)
{
foreach (var h in headers)
{
client.DefaultRequestHeaders.Add(h.Key, h.Value);
}
}
response = await client.GetAsync(uri);
}
return response;
}
public async Task<T> GetAsync<T>(string uri, Dictionary<string, string> headers = null)
{
...
rawResponse = await GetAsync(uri, headers);
...
}
}
这个笑话是:
[TestInitialize]
public void Initialize()
{
...
_httpHelper = new Mock<HttpHelper>(_logHelper.Object) { CallBase = true };
...
}
[TestMethod]
public async Task SuccessStatusCode_WithAuthHeader()
{
...
_httpHelper.Setup(m => m.GetAsync(_uri, myHeaders)).Returns(
Task<HttpResponseMessage>.Factory.StartNew(() =>
{
return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(JsonConvert.SerializeObject(_testData))
};
})
);
var result = await _httpHelper.Object.GetAsync<TestDTO>(...);
Assert.AreEqual(...);
}
也许在您当前的项目中会有一些代码需要更改,但对于新项目,您绝对应该考虑使用Flurl。
https://flurl.dev
它是一个。net的HTTP客户端库,具有一个流畅的接口,特别支持使用它来发出HTTP请求的代码的可测试性。
网站上有很多代码示例,但简单地说,你在代码中是这样使用的。
添加用途。
using Flurl;
using Flurl.Http;
发送get请求并读取响应。
public async Task SendGetRequest()
{
var response = await "https://example.com".GetAsync();
// ...
}
在单元测试中,Flurl充当一个模拟,可以将其配置为所需的行为,还可以验证所执行的调用。
using (var httpTest = new HttpTest())
{
// Arrange
httpTest.RespondWith("OK", 200);
// Act
await sut.SendGetRequest();
// Assert
httpTest.ShouldHaveCalled("https://example.com")
.WithVerb(HttpMethod.Get);
}
我的一个同事注意到,大多数HttpClient方法都在底层调用SendAsync(HttpRequestMessage request, CancellationToken CancellationToken),这是HttpMessageInvoker的一个虚拟方法:
所以到目前为止,模拟HttpClient最简单的方法就是简单地模拟这个特定的方法:
var mockClient = new Mock<HttpClient>();
mockClient.Setup(client => client.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>())).ReturnsAsync(_mockResponse.Object);
并且您的代码可以调用大多数(但不是全部)HttpClient类方法,包括常规方法
httpClient.SendAsync(req)
点击这里确认
https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
如果您不介意运行自己的http服务器,可以尝试Xim。其实很简单:
using Xim.Simulators.Api;
[Test]
public async Task TestHttpGetMethod()
{
using var simulation = Simulation.Create();
using var api = simulation
.AddApi()
.AddHandler("GET /books/1234", ApiResponse.Ok())
.Build();
await api.StartAsync();
var httpClient = new HttpClient();
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"{api.Location}/books/1234"));
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.IsTrue(api.ReceivedApiCalls.Any(call => call.Action == "GET /books/1234"));
}
这是使用模拟的一个很好的替代方案,可能适合您在某些场景中的需求。它建立在Kestrel的基础上(是的,我是作者)。