问题
I have some issues trying to wrap my code to be used in unit tests. The issues is this. I Have the interface IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
And the class using it, HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
And then the Connection class, which uses simpleIOC to inject the client implementation:
public class Connection
{
private IHttpHandler _httpClient;
public Connection(IHttpHandler httpClient)
{
_httpClient = httpClient;
}
}
And then I have a unit test project which has this class:
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?
}
Now obviously I will have methods in the Connection class that will retrieve data (JSON) from a my back end. However, I want to write unit tests for this class, and obviously I don't want to write tests against the real back end, rather a mocked one. I Have tried to google a good answer to this without great success. I can and have used Moq to mock before, but never on something like httpClient. How Should I approach this problem?
Thanks in advance.
回答1:
Your interface exposes the concrete HttpClient
class, therefore any classes that use this interface are tied to it, this means that it cannot be mocked.
HttpClient
does not inherit from any interface so you will have to write your own. I suggest a decorator-like pattern:
public interface IHttpHandler
{
HttpResponseMessage Get(string url);
HttpResponseMessage Post(string url, HttpContent content);
Task<HttpResponseMessage> GetAsync(string url);
Task<HttpResponseMessage> PostAsync(string url, HttpContent content);
}
And your class will look like this:
public class HttpClientHandler : IHttpHandler
{
private HttpClient _client = new HttpClient();
public HttpResponseMessage Get(string url)
{
return GetAsync(url).Result;
}
public HttpResponseMessage Post(string url, HttpContent content)
{
return PostAsync(url, content).Result;
}
public async Task<HttpResponseMessage> GetAsync(string url)
{
return await _client.GetAsync(url);
}
public async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
{
return await _client.PostAsync(url, content);
}
}
The point in all of this is that HttpClientHandler
creates its own HttpClient
, you could then of course create multiple classes that implement IHttpHandler
in different ways.
The main issue with this approach is that you are effectively writing a class that just calls methods in another class, however you could create a class that inherits from HttpClient
(See Nkosi's example, it's a much better approach than mine). Life would be much easier if HttpClient
had an interface that you could mock, unfortunately it does not.
This example is not the golden ticket however. IHttpHandler
still relies on HttpResponseMessage
, which belongs to System.Net.Http
namespace, therefore if you do need other implementations other than HttpClient
, you will have to perform some kind of mapping to convert their responses into HttpResponseMessage
objects. This of course is only a problem if you need to use multiple implementations of IHttpHandler
but it doesn't look like you do so it's not the end of the world, but it's something to think about.
Anyway, you can simply mock IHttpHandler
without having to worry about the concrete HttpClient
class as it has been abstracted away.
I recommend testing the non-async methods, as these still call the asynchronous methods but without the hassle of having to worry about unit testing asynchronous methods, see here
回答2:
HttpClient's extensibility lies in the HttpMessageHandler
passed to the constructor. Its intent is to allow platform specific implementations, but you can also mock it. There's no need to create a decorator wrapper for HttpClient.
If you'd prefer a DSL to using Moq, I have a library up on GitHub/Nuget that makes things a little easier: https://github.com/richardszalay/mockhttp
var mockHttp = new MockHttpMessageHandler();
// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON
// Inject the handler or client into your application code
var client = new HttpClient(mockHttp);
var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;
var json = await response.Content.ReadAsStringAsync();
// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}
回答3:
I agree with some of the other answers that the best approach is to mock HttpMessageHandler rather than wrap HttpClient. This answer is unique in that it still injects HttpClient, allowing it to be a singleton or managed with dependency injection.
"HttpClient is intended to be instantiated once and re-used throughout the life of an application." (Source).
Mocking HttpMessageHandler can be a little tricky because SendAsync is protected. Here's a complete example, using xunit and Moq.
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
using Xunit;
// Use nuget to install xunit and Moq
namespace MockHttpClient {
class Program {
static void Main(string[] args) {
var analyzer = new SiteAnalyzer(Client);
var size = analyzer.GetContentSize("http://microsoft.com").Result;
Console.WriteLine($"Size: {size}");
}
private static readonly HttpClient Client = new HttpClient(); // Singleton
}
public class SiteAnalyzer {
public SiteAnalyzer(HttpClient httpClient) {
_httpClient = httpClient;
}
public async Task<int> GetContentSize(string uri)
{
var response = await _httpClient.GetAsync( uri );
var content = await response.Content.ReadAsStringAsync();
return content.Length;
}
private readonly HttpClient _httpClient;
}
public class SiteAnalyzerTests {
[Fact]
public async void GetContentSizeReturnsCorrectLength() {
// Arrange
const string testContent = "test content";
var mockMessageHandler = new Mock<HttpMessageHandler>();
mockMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage {
StatusCode = HttpStatusCode.OK,
Content = new StringContent(testContent)
});
var underTest = new SiteAnalyzer(new HttpClient(mockMessageHandler.Object));
// Act
var result = await underTest.GetContentSize("http://anyurl");
// Assert
Assert.Equal(testContent.Length, result);
}
}
}
回答4:
This is a common question, and I was heavily on the side wanting the ability to mock HttpClient, but I think I finally came to the realization that you shouldn't be mocking HttpClient. It seems logical to do so, but I think we've been brainwashed by things we see in open source libraries.
We often see "Clients" out there that we mock in our code so that we can test in isolation, so we automatically try to apply the same principle to HttpClient. HttpClient actually does a lot; you can think of it as a manager for HttpMessageHandler, so you don't wanna mock that, and that's why it still doesn't have an interface. The part that you're really interested in for unit testing, or designing your services, even, is the HttpMessageHandler since that is what returns the response, and you can mock that.
It's also worth pointing out that you should probably start treating HttpClient like a bigger deal. For example: Keep your instatiating of new HttpClients to a minimum. Reuse them, they're designed to be reused and use a crap ton less resources if you do. If you start treating it like a bigger deal, it'll feel much more wrong wanting to mock it and now the message handler will start to be the thing that you're injecting, not the client.
In other words, design your dependencies around the handler instead of the client. Even better, abstract "services" that use HttpClient which allow you to inject a handler, and use that as your injectable dependency instead. Then in your tests, you can fake the handler to control the response for setting up your tests.
Wrapping HttpClient is an insane waste of time.
Update: See Joshua Dooms's example. It's exactly what I'm recommending.
回答5:
As also mentioned in the comments you need to abstract away the HttpClient
so as not to be coupled to it. I've done something similar in the past. I'll try to adapt what I did with what you are trying to do.
First look at the HttpClient
class and decided on what functionality it provided that would be needed.
Here is a possibility:
public interface IHttpClient {
System.Threading.Tasks.Task<T> DeleteAsync<T>(string uri) where T : class;
System.Threading.Tasks.Task<T> DeleteAsync<T>(Uri uri) where T : class;
System.Threading.Tasks.Task<T> GetAsync<T>(string uri) where T : class;
System.Threading.Tasks.Task<T> GetAsync<T>(Uri uri) where T : class;
System.Threading.Tasks.Task<T> PostAsync<T>(string uri, object package);
System.Threading.Tasks.Task<T> PostAsync<T>(Uri uri, object package);
System.Threading.Tasks.Task<T> PutAsync<T>(string uri, object package);
System.Threading.Tasks.Task<T> PutAsync<T>(Uri uri, object package);
}
Again as stated before this was for particular purposes. I completely abstracted away most dependencies to anything dealing with HttpClient
and focused on what I wanted returned. You should evaluate how you want to abstract the HttpClient
to provide only the necessary functionality you want.
This will now allow you to mock only what is needed to be tested.
I would even recommend doing away with IHttpHandler
completely and use the HttpClient
abstraction IHttpClient
. But I'm just not picking as you can replace the body of your handler interface with the members of the abstracted client.
An implementation of the IHttpClient
can then be used to wrapp/adapt a real/concrete HttpClient
or any other object for that matter, that can be used to make HTTP requests as what you really wanted was a service that provided that functionality as apposed to HttpClient
specifically. Using the abstraction is a clean (My opinion) and SOLID approach and can make your code more maintainable if you need to switch out the underlying client for something else as the framework changes.
Here is a snippet of how an implementation could be done.
/// <summary>
/// HTTP Client adaptor wraps a <see cref="System.Net.Http.HttpClient"/>
/// that contains a reference to <see cref="ConfigurableMessageHandler"/>
/// </summary>
public sealed class HttpClientAdaptor : IHttpClient {
HttpClient httpClient;
public HttpClientAdaptor(IHttpClientFactory httpClientFactory) {
httpClient = httpClientFactory.CreateHttpClient(**Custom configurations**);
}
//...other code
/// <summary>
/// Send a GET request to the specified Uri as an asynchronous operation.
/// </summary>
/// <typeparam name="T">Response type</typeparam>
/// <param name="uri">The Uri the request is sent to</param>
/// <returns></returns>
public async System.Threading.Tasks.Task<T> GetAsync<T>(Uri uri) where T : class {
var result = default(T);
//Try to get content as T
try {
//send request and get the response
var response = await httpClient.GetAsync(uri).ConfigureAwait(false);
//if there is content in response to deserialize
if (response.Content.Headers.ContentLength.GetValueOrDefault() > 0) {
//get the content
string responseBodyAsText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
//desrialize it
result = deserializeJsonToObject<T>(responseBodyAsText);
}
} catch (Exception ex) {
Log.Error(ex);
}
return result;
}
//...other code
}
As you can see in the above example, a lot of the heavy lifting usually associated with using HttpClient
is hidden behind the abstraction.
You connection class can then be inject with the abstracted client
public class Connection
{
private IHttpClient _httpClient;
public Connection(IHttpClient httpClient)
{
_httpClient = httpClient;
}
}
Your test can then mock what is needed for your SUT
private IHttpClient _httpClient;
[TestMethod]
public void TestMockConnection()
{
SomeModelObject model = new SomeModelObject();
var httpClientMock = new Mock<IHttpClient>();
httpClientMock.Setup(c => c.GetAsync<SomeModelObject>(It.IsAny<string>()))
.Returns(() => Task.FromResult(model));
_httpClient = httpClientMock.Object;
var client = new Connection(_httpClient);
// Assuming doSomething uses the client to make
// a request for a model of type SomeModelObject
client.doSomething();
}
回答6:
Building on the other answers, I suggest this code, which does not have any outside dependencies:
[TestClass]
public class MyTestClass
{
[TestMethod]
public async Task MyTestMethod()
{
var httpClient = new HttpClient(new MockHttpMessageHandler());
var content = await httpClient.GetStringAsync("http://some.fake.url");
Assert.AreEqual("Content as string", content);
}
}
public class MockHttpMessageHandler : HttpMessageHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var responseMessage = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("Content as string")
};
return await Task.FromResult(responseMessage);
}
}
回答7:
I think the issue is that you've got it just a little upside down.
public class AuroraClient : IAuroraClient
{
private readonly HttpClient _client;
public AuroraClient() : this(new HttpClientHandler())
{
}
public AuroraClient(HttpMessageHandler messageHandler)
{
_client = new HttpClient(messageHandler);
}
}
If you look at the class above, I think this is what you want. Microsoft recommends keeping the client alive for optimal performance, so this type of structure allows you to do that. Also the HttpMessageHandler is an abstract class and therefore mockable. Your test method would then look like this:
[TestMethod]
public void TestMethod1()
{
// Arrange
var mockMessageHandler = new Mock<HttpMessageHandler>();
// Set up your mock behavior here
var auroraClient = new AuroraClient(mockMessageHandler.Object);
// Act
// Assert
}
This allows you to test your logic while mocking the HttpClient's behavior.
Sorry guys, after writing this and trying it myself, I realized that you can't mock the protected methods on the HttpMessageHandler. I subsequently added the following code to allow for injection of a proper mock.
public interface IMockHttpMessageHandler
{
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}
public class MockHttpMessageHandler : HttpMessageHandler
{
private readonly IMockHttpMessageHandler _realMockHandler;
public MockHttpMessageHandler(IMockHttpMessageHandler realMockHandler)
{
_realMockHandler = realMockHandler;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await _realMockHandler.SendAsync(request, cancellationToken);
}
}
The tests written with this then look something like the following:
[TestMethod]
public async Task GetProductsReturnsDeserializedXmlXopData()
{
// Arrange
var mockMessageHandler = new Mock<IMockHttpMessageHandler>();
// Set up Mock behavior here.
var auroraClient = new AuroraClient(new MockHttpMessageHandler(mockMessageHandler.Object));
// Act
// Assert
}
回答8:
One of my colleagues noticed that most of the HttpClient
methods all call SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
under the hood, which is a virtual method off of HttpMessageInvoker
:
So by far the easiest way to mock out HttpClient
was to simply mock that particular method:
var mockClient = new Mock<HttpClient>();
mockClient.Setup(client => client.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>())).ReturnsAsync(_mockResponse.Object);
and your code can call most (but not all) of the HttpClient
class methods, including a regular
httpClient.SendAsync(req)
Check here to confirm https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
回答9:
One alternative would be to setup a stub HTTP server that returns canned responses based on pattern matching the request url, meaning you test real HTTP requests not mocks. Historically this would have taken significant develoment effort and would have been far to slow to be considered for unit testing, however OSS library WireMock.net is easy to use and fast enough to be run with lots of tests so may be worth considering. Setup is a few lines of code:
var server = FluentMockServer.Start();
server.Given(
Request.Create()
.WithPath("/some/thing").UsingGet()
)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody("{'attr':'value'}")
);
You can find a more details and guidance on using wiremock in tests here.
回答10:
Joining the party a bit late, but I like using wiremocking (https://github.com/WireMock-Net/WireMock.Net) whenever possible in the integrationtest of a dotnet core microservice with downstream REST dependencies.
By implementing a TestHttpClientFactory extending the IHttpClientFactory we can override the method
HttpClient CreateClient(string name)
So when using the named clients within your app you are in control of returning a HttpClient wired to your wiremock.
The good thing about this approach is that you are not changing anything within the application you are testing, and enables course integration tests doing an actual REST request to your service and mocking the json (or whatever) the actual downstream request should return. This leads to concise tests and as little mocking as possible in your application.
public class TestHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
var httpClient = new HttpClient
{
BaseAddress = new Uri(G.Config.Get<string>($"App:Endpoints:{name}"))
// G.Config is our singleton config access, so the endpoint
// to the running wiremock is used in the test
};
return httpClient;
}
}
and
// in bootstrap of your Microservice
IHttpClientFactory factory = new TestHttpClientFactory();
container.Register<IHttpClientFactory>(factory);
回答11:
Here's a simple solution, which worked well for me.
Using the moq mocking library.
// ARRANGE
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
.Protected()
// Setup the PROTECTED method to mock
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
// prepare the expected response of the mocked http call
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("[{'id':1,'value':'1'}]"),
})
.Verifiable();
// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
BaseAddress = new Uri("http://test.com/"),
};
var subjectUnderTest = new MyTestClass(httpClient);
// ACT
var result = await subjectUnderTest
.GetSomethingRemoteAsync('api/test/whatever');
// ASSERT
result.Should().NotBeNull(); // this is fluent assertions here...
result.Id.Should().Be(1);
// also check the 'http' call was like we expected it
var expectedUri = new Uri("http://test.com/api/test/whatever");
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(1), // we expected a single external request
ItExpr.Is<HttpRequestMessage>(req =>
req.Method == HttpMethod.Get // we expected a GET request
&& req.RequestUri == expectedUri // to this uri
),
ItExpr.IsAny<CancellationToken>()
);
Source: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
回答12:
I'm not convinced by many of the answers.
First of all, imagine you want to unit test a method that uses HttpClient
. You should not instantiate HttpClient
directly in your implementation. You should inject a factory with the responsibility of providing an instance of HttpClient
for you. That way you can mock later on that factory and return whichever HttpClient
you want (e.g: a mock HttpClient
and not the real one).
So, you would have a factory like the following:
public interface IHttpClientFactory
{
HttpClient Create();
}
And an implementation:
public class HttpClientFactory
: IHttpClientFactory
{
public HttpClient Create()
{
var httpClient = new HttpClient();
return httpClient;
}
}
Of course you would need to register in your IoC Container this implementation. If you use Autofac it would be something like:
builder
.RegisterType<IHttpClientFactory>()
.As<HttpClientFactory>()
.SingleInstance();
Now you would have a proper and testeable implementation. Imagine that your method is something like:
public class MyHttpClient
: IMyHttpClient
{
private readonly IHttpClientFactory _httpClientFactory;
public SalesOrderHttpClient(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<string> PostAsync(Uri uri, string content)
{
using (var client = _httpClientFactory.Create())
{
var clientAddress = uri.GetLeftPart(UriPartial.Authority);
client.BaseAddress = new Uri(clientAddress);
var content = new StringContent(content, Encoding.UTF8, "application/json");
var uriAbsolutePath = uri.AbsolutePath;
var response = await client.PostAsync(uriAbsolutePath, content);
var responseJson = response.Content.ReadAsStringAsync().Result;
return responseJson;
}
}
}
Now the testing part. HttpClient
extends HttpMessageHandler
, which is abstract. Let's create a "mock" of HttpMessageHandler
that accepts a delegate so that when we use the mock we can also setup each behaviour for each test.
public class MockHttpMessageHandler
: HttpMessageHandler
{
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _sendAsyncFunc;
public MockHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> sendAsyncFunc)
{
_sendAsyncFunc = sendAsyncFunc;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await _sendAsyncFunc.Invoke(request, cancellationToken);
}
}
And now, and with the help of Moq (and FluentAssertions, a library that makes unit tests more readable), we have everything needed to unit test our method PostAsync that uses HttpClient
public static class PostAsyncTests
{
public class Given_A_Uri_And_A_JsonMessage_When_Posting_Async
: Given_WhenAsync_Then_Test
{
private SalesOrderHttpClient _sut;
private Uri _uri;
private string _content;
private string _expectedResult;
private string _result;
protected override void Given()
{
_uri = new Uri("http://test.com/api/resources");
_content = "{\"foo\": \"bar\"}";
_expectedResult = "{\"result\": \"ok\"}";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var messageHandlerMock =
new MockHttpMessageHandler((request, cancellation) =>
{
var responseMessage =
new HttpResponseMessage(HttpStatusCode.Created)
{
Content = new StringContent("{\"result\": \"ok\"}")
};
var result = Task.FromResult(responseMessage);
return result;
});
var httpClient = new HttpClient(messageHandlerMock);
httpClientFactoryMock
.Setup(x => x.Create())
.Returns(httpClient);
var httpClientFactory = httpClientFactoryMock.Object;
_sut = new SalesOrderHttpClient(httpClientFactory);
}
protected override async Task WhenAsync()
{
_result = await _sut.PostAsync(_uri, _content);
}
[Fact]
public void Then_It_Should_Return_A_Valid_JsonMessage()
{
_result.Should().BeEquivalentTo(_expectedResult);
}
}
}
Obviously this test is silly, and we're really testing our mock. But you get the idea. You should test meaningful logic depending on your implementation such as..
- if the code status of the response is not 201, should it throw an exception?
- if the response text cannot be parsed, what should happen?
- etc.
The purpose of this answer was to test something that uses HttpClient and this is a nice clean way to do so.
回答13:
You could use RichardSzalay MockHttp library which mocks the HttpMessageHandler and can return an HttpClient object to be used during tests.
GitHub MockHttp
PM> Install-Package RichardSzalay.MockHttp
From the GitHub documentation
MockHttp defines a replacement HttpMessageHandler, the engine that drives HttpClient, that provides a fluent configuration API and provides a canned response. The caller (eg. your application's service layer) remains unaware of its presence.
Example from GitHub
var mockHttp = new MockHttpMessageHandler();
// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON
// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();
var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;
var json = await response.Content.ReadAsStringAsync();
// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}
回答14:
This is an old question, but I feel the urge to extend the answers with a solution I didn't see here.
You can fake the Microsoft assemly (System.Net.Http) and then use ShinsContext during the test.
- In VS 2017, right click on the System.Net.Http assembly and choose "Add Fakes Assembly"
- Put your code in the unit test method under a ShimsContext.Create() using. This way, you can isolate the code where you are planning to fake the HttpClient.
Depends on your implementation and test, I would suggest to implement all the desired acting where you call a method on the HttpClient and want to fake the returned value. Using ShimHttpClient.AllInstances will fake your implementation in all the instances created during your test. For example, if you want to fake the GetAsync() method, do the following:
[TestMethod] public void FakeHttpClient() { using (ShimsContext.Create()) { System.Net.Http.Fakes.ShimHttpClient.AllInstances.GetAsyncString = (c, requestUri) => { //Return a service unavailable response var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable); var task = Task.FromResult(httpResponseMessage); return task; }; //your implementation will use the fake method(s) automatically var client = new Connection(_httpClient); client.doSomething(); } }
回答15:
I did something very simple, as I was in a DI environment.
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);
...
}
}
and the mock is:
[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(...);
}
回答16:
All you need is a test version of HttpMessageHandler
class which you pass to HttpClient
ctor. The main point is that your test HttpMessageHandler
class will have a HttpRequestHandler
delegate that the callers can set and simply handle the HttpRequest
the way they want.
public class FakeHttpMessageHandler : HttpMessageHandler
{
public Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> HttpRequestHandler { get; set; } =
(r, c) =>
new HttpResponseMessage
{
ReasonPhrase = r.RequestUri.AbsoluteUri,
StatusCode = HttpStatusCode.OK
};
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(HttpRequestHandler(request, cancellationToken));
}
}
You can use an instance of this class to create a concrete HttpClient instance. Via the HttpRequestHandler delegate you have full control over outgoing http requests from HttpClient.
回答17:
Inspired by PointZeroTwo's answer, here's a sample using NUnit and FakeItEasy.
SystemUnderTest
in this example is the class that you want to test - no sample content given for it but I assume you have that already!
[TestFixture]
public class HttpClientTests
{
private ISystemUnderTest _systemUnderTest;
private HttpMessageHandler _mockMessageHandler;
[SetUp]
public void Setup()
{
_mockMessageHandler = A.Fake<HttpMessageHandler>();
var httpClient = new HttpClient(_mockMessageHandler);
_systemUnderTest = new SystemUnderTest(httpClient);
}
[Test]
public void HttpError()
{
// Arrange
A.CallTo(_mockMessageHandler)
.Where(x => x.Method.Name == "SendAsync")
.WithReturnType<Task<HttpResponseMessage>>()
.Returns(Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.InternalServerError,
Content = new StringContent("abcd")
}));
// Act
var result = _systemUnderTest.DoSomething();
// Assert
// Assert.AreEqual(...);
}
}
来源:https://stackoverflow.com/questions/36425008/mocking-httpclient-in-unit-tests