Издевательство над HttpClient в модульных тестах
у меня есть некоторые проблемы, пытаясь обернуть мой код для использования в модульных тестах. Проблема вот в чем. У меня есть интерфейс IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
и класс, использующий его, HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
а затем класс соединения, который использует 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?
}
теперь очевидно, что у меня будут методы в классе соединения, которые будут извлечение данных (JSON) из моей задней части. Однако я хочу написать модульные тесты для этого класса, и, очевидно, я не хочу писать тесты против реального бэк-энда, а скорее издеваться. Я попытался google хороший ответ на это без большого успеха. Я могу и раньше использовать Moq для издевательств, но никогда не на чем-то вроде httpClient. Как я должен подойти к этой проблеме?
спасибо заранее.
14 ответов:
ваш интерфейс выставляет бетон
HttpClient
класс, поэтому любые классы, которые используют этот интерфейс привязаны к нему, это означает, что он не может быть высмеян.
HttpClient
не наследует от любой интерфейс, так что вам придется написать свой собственный. Я предлагаю декоратор-как шаблон: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); }
и ваш класс будет выглядеть так:
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); } }
смысл всего этого в том, что
HttpClientHandler
создает свой собственныйHttpClient
, тогда вы можете конечно, создать несколько классов, которые реализуютIHttpHandler
по-разному.основная проблема с этим подходом заключается в том, что вы эффективно пишете класс, который просто вызывает методы в другом классе, однако вы можете создать класс, который наследует С
HttpClient
(см. Nkosiбыл это, это гораздо лучший подход, чем у меня). Жизнь была бы намного проще, еслиHttpClient
был интерфейс, который вы могли бы издеваться, к сожалению, это не так.этот пример не золотой билет, однако.
IHttpHandler
все еще полагается наHttpResponseMessage
, который принадлежитSystem.Net.Http
пространство имен, поэтому, если вам нужны другие реализации, отличные отHttpClient
, вам придется выполнить какое-то отображение, чтобы преобразовать свои ответы вHttpResponseMessage
объекты. Это, конечно, только проблема если вам нужно использовать несколько реализаций наIHttpHandler
но это не похоже на то, что вы делаете, так что это не конец света, но это что-то думать о.в любом случае, вы можете просто издеваться над
IHttpHandler
, не беспокоясь о конкретныхHttpClient
класс, как это было абстрагировать.я рекомендую проверить неасинхронным методы, поскольку они по-прежнему вызывают асинхронные методы, но без хлопот о том, чтобы беспокоиться об асинхронных методах модульного тестирования, см. здесь
расширяемость HttpClient заключается в
HttpMessageHandler
передается в конструктор. Его цель состоит в том, чтобы разрешить конкретные реализации Платформы, но вы также можете издеваться над ней. Там нет необходимости, чтобы создать оболочку декоратор для класса HttpClient.Если вы предпочитаете DSL для использования Moq, у меня есть библиотека на GitHub/Nuget, что делает вещи немного проще: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'}
Это распространенный вопрос, и я был сильно на стороне, желая возможность издеваться над HttpClient, но я думаю, что наконец пришел к пониманию того, что вы не должны издеваться над HttpClient. Это кажется логичным, но я думаю, что нам промыли мозги вещи, которые мы видим в библиотеках с открытым исходным кодом.
мы часто видим "клиентов", которые мы высмеиваем в нашем коде, чтобы мы могли тестировать изолированно, поэтому мы автоматически пытаемся применить тот же принцип к HttpClient. HttpClient на самом деле делает много; вы можете думать об этом как о менеджере для HttpMessageHandler, поэтому вы не хотите издеваться над этим, и именно поэтому он еще не имеет интерфейса. Часть, которая вас действительно интересует для модульного тестирования или разработки ваших услуг, даже является HttpMessageHandler, так как это то, что возвращает ответ, и вы можете издеваться.
также стоит отметить, что вы, вероятно, должны начать относиться к HttpClient как к более крупной сделке. Например: Сведите к минимуму установку новых HttpClients. Повторное использование их, они предназначены для повторного использования и использовать дерьмо тонну меньше ресурсов, если вы делаете. Если вы начнете относиться к нему как к большой сделке, он будет чувствовать себя гораздо более неправильно, желая издеваться над ним, и теперь обработчик сообщений начнет быть тем, что вы вводите, а не клиентом.
другими словами, создайте свои зависимости вокруг обработчика вместо клиента. Еще лучше, абстрактные "услуги", которые используют HttpClient, которые позволяют вам внедрить обработчик и использовать его в качестве инъекций зависимостей вместо. Затем в ваших тестах вы можете подделать обработчик, чтобы контролировать ответ для настройки ваших тестов.
обертывание HttpClient-это безумная трата времени.
обновление: Пример см. Иисус Навин обрекает это. Это именно то, что я рекомендую.
Я согласен с некоторыми другими ответами, что лучший подход-это издеваться над HttpMessageHandler, а не обертывать HttpClient. Этот ответ уникален тем, что он все еще вводит HttpClient, позволяя ему быть одноэлементным или управляемым с помощью инъекции зависимостей.
"HttpClient предназначен для однократного создания экземпляра и повторного использования в течение всего срока службы приложения."(источник).
насмешливый HttpMessageHandler может быть немного сложнее, потому что SendAsync защищенный. Вот полный пример, используя xunit и 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); } } }
Как также упоминалось в комментариях вам нужно аннотация до
HttpClient
чтобы не быть связанным с ним. Я делал что-то подобное в прошлом. Я постараюсь приспособить то, что я сделал с тем, что вы пытаетесь сделать.первый взгляд на
HttpClient
класс и решил, какую функциональность он предоставил, что будет необходимо.вот вариант:
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); }
опять же, как было указано ранее, это было для конкретных целей. Я полностью абстрагировано большинство зависимостей от всего, что имеет дело с
HttpClient
и сосредоточился на том, что я хотел вернуть. Вы должны оценить, как вы хотите абстрагироватьHttpClient
чтобы обеспечить только необходимую функциональность, которую вы хотите.теперь это позволит вам издеваться только над тем, что необходимо проверить.
я бы даже рекомендовал покончить с
IHttpHandler
полностью и использоватьHttpClient
абстрагированиеIHttpClient
. Но я просто не выбираю, как вы можете заменить тело вашего обработчика интерфейс с членами абстрактного клиента.реализация
IHttpClient
затем можно использовать для обертывания / адаптации реального / конкретногоHttpClient
или любой другой объект в этом отношении, который может быть использован для выполнения HTTP-запросов, поскольку то, что вы действительно хотели, было сервисом, который обеспечивал эту функциональность как приложение кHttpClient
специально. Использование абстракции является чистым (мое мнение) и солидным подходом и может сделать ваш код более удобным для обслуживания, если вам нужно отключить базовое клиент для чего-то еще, как меняется структура.вот фрагмент того, как реализация может быть выполнена.
/// <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 }
как вы можете видеть в приведенном выше примере, многие тяжелые подъемы обычно связаны с использованием
HttpClient
скрывается за абстракцией.вы класс соединения можно затем ввести с абстрактным клиентом
public class Connection { private IHttpClient _httpClient; public Connection(IHttpClient httpClient) { _httpClient = httpClient; } }
ваш тест может затем издеваться над тем, что необходимо для вашего 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(); }
основываясь на других ответах, я предлагаю этот код, который не имеет никаких внешних зависимостей:
[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); } }
Я думаю, что проблема в том, что у вас есть это просто немного вверх ногами.
public class AuroraClient : IAuroraClient { private readonly HttpClient _client; public AuroraClient() : this(new HttpClientHandler()) { } public AuroraClient(HttpMessageHandler messageHandler) { _client = new HttpClient(messageHandler); } }
если вы посмотрите на класс выше, я думаю, что это то, что вы хотите. Корпорация Майкрософт рекомендует поддерживать клиент для оптимальной производительности, поэтому этот тип структуры позволяет это сделать. Также HttpMessageHandler является абстрактным классом и, следовательно, из. Ваш метод тестирования будет выглядеть следующим образом:
[TestMethod] public void TestMethod1() { // Arrange var mockMessageHandler = new Mock<HttpMessageHandler>(); // Set up your mock behavior here var auroraClient = new AuroraClient(mockMessageHandler.Object); // Act // Assert }
Это позволяет проверить вашу логику, издеваясь над Поведение HttpClient.
Извините, ребята, после написания этого и попробовать его Сам, я понял, что вы не можете издеваться над защищенными методами на HttpMessageHandler. Впоследствии я добавил следующий код, чтобы обеспечить инъекцию правильного макета.
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); } }
тесты, написанные с этим, выглядят примерно так:
[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 }
один из моих коллег заметил, что большинство
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-сервера, который возвращает консервированные ответы на основе шаблона, соответствующего url-адресу запроса, что означает, что вы тестируете реальные HTTP-запросы, а не издевается. Исторически это потребовало бы значительных усилий по разработке и было бы далеко медленным, чтобы рассматриваться для модульного тестирования, однако библиотека OSS WireMock.net прост в использовании и достаточно быстро, чтобы работать с большим количеством тестов, так что может быть стоит рассмотреть. Настройка состоит из нескольких строк код:
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'}") );
вы можете найти более подробную информацию и руководство по использованию wiremock в тестах здесь.
вы можете использовать RichardSzalay MockHttp библиотека, которая издевается над HttpMessageHandler и может возвращать объект HttpClient, который будет использоваться во время тестов.
PM> Install-Package RichardSzalay.MockHttp
MockHttp определяет замену HttpMessageHandler, движок, который управляет HttpClient, который предоставляет свободный API конфигурации и обеспечивает отклика. Вызывающий абонент (например. сервисный слой приложения) остается в неведении о его присутствии.
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'}
Это старый вопрос, но я чувствую желание расширить ответы с решением, которое я не видел здесь.
Вы можете подделать Microsoft assemly (System.Net.Http), а затем использовать ShinsContext во время теста.
- в VS 2017, щелкните правой кнопкой мыши на сборке System.Net.Http и выберите "Добавить сборку подделок"
- поместите свой код в метод модульного теста под ShimsContext.Создать () с помощью. Таким образом, вы можете изолировать код, где вы планируете фейк HttpClient.
зависит от вашей реализации и теста, я бы предложил реализовать все желаемые действия, где вы вызываете метод на HttpClient и хотите подделать возвращаемое значение. Использование ShimHttpClient.AllInstances будет подделывать вашу реализацию во всех экземплярах, созданных во время вашего теста. Например, если вы хотите подделать метод GetAsync (), выполните следующие действия:
[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(); } }
Я сделал что-то очень простое, так как я был в среде DI.
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(...); }
я не убежден во многих ответах.
прежде всего, представьте, что вы хотите провести модульное тестирование метода, который использует
HttpClient
. Вы не должны создавать экземплярHttpClient
непосредственно в вашей реализации. Вы должны ввести фабрику с ответственностью за предоставление экземпляраHttpClient
для вас. Таким образом, вы можете издеваться позже на этой фабрике и возвращать любойHttpClient
вы хотите (например: макетHttpClient
и не реальный).Так, вы имели бы фабрику как следующее:
public interface IHttpClientFactory { HttpClient Create(); }
и реализация:
public class HttpClientFactory : IHttpClientFactory { public HttpClient Create() { var httpClient = new HttpClient(); return httpClient; } }
конечно, вам нужно будет зарегистрировать в своем контейнере IoC эту реализацию. Если вы используете Autofac это будет что-то вроде:
builder .RegisterType<IHttpClientFactory>() .As<HttpClientFactory>() .SingleInstance();
теперь у вас будет правильная и проверяемая реализация. Представьте, что ваш метод-это что-то вроде:
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; } } }
теперь тестовая часть.
HttpClient
выходитHttpMessageHandler
, который является абстрактным. Давайте создадим "макет" изHttpMessageHandler
который принимает a делегируйте, чтобы при использовании макета мы также могли настроить каждое поведение для каждого теста.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); } }
и теперь, и с помощью Moq (и FluentAssertions, библиотека, которая делает модульные тесты более читабельными), у нас есть все необходимое для модульного тестирования нашего метода PostAsync, который использует
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); } } }
очевидно, что этот тест глуп, и мы действительно тестируем наш макет. Но вы поняли идею. Вы должны проверить осмысленную логику в зависимости от вашей реализации такой как..
- если состояние кода ответа не 201, должен ли он вызвать исключение?
- если текст ответа не может быть проанализирован, что должно произойти?
- etc.
цель этого ответа было проверить что-то, что использует HttpClient и это хороший чистый способ сделать это.
все, что вам нужно, это тестовая версия
HttpMessageHandler
класс, который вы передаетеHttpClient
ctor. Главное, что ваш тестHttpMessageHandler
класс будет иметьHttpRequestHandler
делегировать, что абоненты могут установить и просто обрабатыватьHttpRequest
так, как они хотят.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)); } }
вы можете использовать экземпляр этого класса для создания конкретного экземпляра HttpClient. Через делегат HttpRequestHandler вы имеете полный контроль над исходящими http-запросами от HttpClient.