MyLab.ApiClient.Test
1.5.7
dotnet add package MyLab.ApiClient.Test --version 1.5.7
NuGet\Install-Package MyLab.ApiClient.Test -Version 1.5.7
<PackageReference Include="MyLab.ApiClient.Test" Version="1.5.7" />
paket add MyLab.ApiClient.Test --version 1.5.7
#r "nuget: MyLab.ApiClient.Test, 1.5.7"
// Install MyLab.ApiClient.Test as a Cake Addin #addin nuget:?package=MyLab.ApiClient.Test&version=1.5.7 // Install MyLab.ApiClient.Test as a Cake Tool #tool nuget:?package=MyLab.ApiClient.Test&version=1.5.7
MyLab.ApiClient.Test
Поддерживаемые платформы: .NET Core 3.1+
Ознакомьтесь с последними изменениями в журнале изменений.
Обзор
MyLab.ApiClient.Test
представляет набор инструментов для написания функциональных и интеграционных тестов на базе xUnit
, связанных с вызовами WEB-API
с использованием MyLab.ApiClient.
ApiClientTest - Базовый класс теста API-клиента
Обзор
Класс ApiClientTest<TStartup, TService>
предназначен для инкапсуляции однотипных операций для тестов, выполняющих обращения к web
-сервисам.
Чтобы воспользоваться этим классом, необходимо:
- унаследовать класс теста от
ApiClientTest
; - указать
TStartup
- классStartup
из проекта веб-приложения; - указать
TService
- интерфейс - контракт сервиса из проекта веб-приложения; - передать в конструктор базового класса объект вывода результатов тестирования
ITestOutputHelper
илиnull
.
Пример исходного теста:
public class ApiClientTestBehaviorBefore : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _appFactory;
private readonly ITestOutputHelper _output;
public ApiClientTestBehaviorBefore(
WebApplicationFactory<Startup> appFactory,
ITestOutputHelper output)
{
_appFactory = appFactory;
_output = output;
}
[Fact]
public async Task ShouldInvokeServerCall()
{
//Arrange
var clProvider = new DelegateHttpClientProvider(
() => _appFactory.CreateClient());
var client = new ApiClient<IWeatherForecastService>(clProvider);
//Act
var detailedResponse = await client
.Call(service => service.Get())
.GetDetailed();
_output.WriteLine(detailedResponse.RequestDump);
_output.WriteLine("");
_output.WriteLine(detailedResponse.ResponseDump);
//Assert
Assert.NotNull(detailedResponse.ResponseContent);
}
}
Результат выполнения теста:
GET http://localhost/WeatherForecast/
Cookie: <empty>
200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 503
Payload here
Тот же тест, переделанный с использованием базового класса ApiClientTest<TStartup, TService>
:
public class ApiClientTestBehaviorAfter : ApiClientTest<Startup, IWeatherForecastService>
{
public ApiClientTestBehaviorAfter(ITestOutputHelper output) : base(output)
{
}
[Fact]
public async Task ShouldInvokeServerCall()
{
//Act
var weather = await TestCall(service => service.Get());
//Assert
Assert.NotNull(weather);
}
}
Результат выполнения теста:
===== REQUEST BEGIN (IWeatherForecastService) =====
GET http://localhost/WeatherForecast/
Cookie: <empty>
===== REQUEST END =====
===== RESPONSE BEGIN (IWeatherForecastService) =====
200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 503
Payload here
===== RESPONSE END =====
Вызов метода API
Вызов метода API
осуществляется асинхронным методом TestCall
. Этот метод имеет перегрузку.
TestCall
- для вызова методов, которые не возвращают результат выполнения;TestCall<TRes>
- для вызова методов, которые возвращают результат выполнения.
В качестве первого и обязательного параметра указывается выражение,которое должно вызывать метод контракта API
. Результат выполнение - детализация вызова CallDetails<TRes>
(CallDetails<string>
- для метода, не возвращающего результат).
При использовании метода TestCall
выполняются следующие действия:
- с помощью фабрики
WebApplicationFactory<TStartup>
создаётся клиентHttpClient
; - на основе него создаётся
ApiClient<TContract>
для отправки запросов вAPI
; - используется переданное выражение и выполнется отправка запроса в `APIёж
- логи дампов запроса и ответа записываются в тестовый лог, если он был передан в конструкторе;
- возвращается детализация вызова
CallDetails<TRes>
.
В примере ниже происходит вызов метода API
:
[Fact]
public async Task ShouldInvokeServerCall()
{
//Act
var val = await TestCall(s => s.AddSalt("test"));
//Assert
Assert.Equal("test-foo", val.ResponseContent);
}
Тестовая настройка сервера
Имеется возможность переопределить зависимости главного DI контейнера веб-приложения. Это можно сделать двумя способами:
- по месту, в методе
TestCall
указать параметрoverrideServices
типаAction<IServiceCollection>
; - для всех тестов класса - переопределив виртуальный метод класса
ApiClientTest
-void OverrideServices(IServiceCollection services)
Очерёдность вызова методов конфигурирования сервисов приложения:
Startup.ConfigureServices
- стандартный метод конфигурирования веб-приложения;ApiClientTest.OverrideServices
- виртуальный метод базового класса теста;TestCall(..., Action<IServiceCollection> overrideServices)
- параметр метода тестового вызоваAPI
.
В примере выше, сервис подмешивает соль к переданному значению, путём добавления "-foo" к значению. В тесте ниже приведён пример переопределения реализации сервиса в веб-приложении, который осуществляет примесь:
[Fact]
public async Task ShouldInvokeWithServiceConfiguration()
{
//Act
var val = await TestCall(
s => s.AddSalt("test"),
overrideServices: srv =>
{
srv.AddSingleton(new StringProcessorService("bar"));
}
);
//Assert
Assert.Equal("test-bar", val.ResponseContent);
}
Вариант с переопределением метода базового класса:
[Fact]
public async Task ShouldInvokeWithServiceConfiguration()
{
//Act
var val = await TestCall(s => s.AddSalt("test"));
//Assert
Assert.Equal("test-bar", val.ResponseContent);
}
protected override void OverrideServices(IServiceCollection srv)
{
srv.AddSingleton(new StringProcessorService("bar"));
}
В этом примере, осуществляется замена уже зарегистрированного синглтона StringProcessorService
на новый, который будет подмешивать другую соль.
Тюнинг HttpClient
Для возможности дополнительно настраивать объект HttpClient
перед использованием, в класс ApiClientTest
добавлены следующие механизмы:
- настройка по месту, в методе
TestCall
нужно указать параметрhttpClientPostInit
типаAction<HttpClient>
; - переопределение метода
HttpClientPostInit(HttpClient httpClient)
, для определения настроек для всех тестов класса.
Очерёдность вызова методов настройки HttpClient
:
ApiClientTest.HttpClientPostInit
- виртуальный метод базового класса теста;TestCall(..., Action<HttpClient> httpClientPostInit)
- параметр метода тестового вызоваAPI
.
В примере ниже приведён пример, где сервис подмешивает соль к переданному значению в заголовке ArgumentHeader
, путём добавления "-foo" к этому значению.
Пример демонстрационный. В реальности, вместо такого хака, лучше добавить в метод контракта API входной строковой параметр и пометить его атрибутом [Header("ArgumentHeader")].
[Fact]
public async Task ShouldInvokeWithHttpModifying()
{
//Act
var val = await TestCall(
s => s.AddSaltToHeader(),
httpClientPostInit: client =>
{
client.DefaultRequestHeaders.Add("ArgumentHeader", "test")
}
);
//Assert
Assert.Equal("test-foo", val.ResponseContent);
}
Вариант с переопределением метода базового класса:
[Fact]
public async Task ShouldInvokeWithHttpModifying()
{
//Act
var val = await TestCall(s => s.AddSaltToHeader());
//Assert
Assert.Equal("test-foo", val.ResponseContent);
}
protected virtual void HttpClientPostInit(HttpClient client)
{
client.DefaultRequestHeaders.Add("ArgumentHeader", "test")
}
TestApi - объектная модель тестового API
Обзор
TestApi
позволяет использовать одно или несколько веб-приложений в тестах.
При использовании TestApi
есть следующие рекомендации:
- создавать объекты
TestApi
в конструкторе класса теста; - создавать по одному объекту
TestApi
на каждыйAPI
; - при инициализации в конструкторе присваивать
ITestOutputHelper
; - при необходимости, во время инициализации в конструкторе, указывать общие для всех тестов класса переопределения сервисов и настройки
HttpClient
через определение полейServiceOverrider
иHttpClientTuner
; - в каждом тестовом методе запускать приложения
API
и получать по клиенту на действующие в этом тесте экземплярыAPI
с помощью методаStart
илиStartWithProxy
классаTestApi
.
Инициализация
public TestApiBehavior(ITestOutputHelper output)
{
_api = new TestApi<Startup, ITestApi>
{
Output = output,
//ServiceOverrider = services => {},
//HttpClientTuner = client => {}
};
}
Использование
Пример использования объекта клиента API:
[Fact]
public async Task ShouldInvokeServerCall()
{
//Arrange
var client = _api.Start(
//serviceOverrider: services => {},
//httpClientTuner: client => {}
);
//Act
var val = await client.Call(service => service.AddSalt("test"));
//Assert
Assert.Equal("test-foo", val.ResponseContent);
}
У метода Start
есть перегрузка для получения внутреннего HttpClient
:
var client = _api.Start(
out var innerClient,
//serviceOverrider: services => {},
//httpClientTuner: client => {}
);
Пример использования прозрачного прокси:
[Fact]
public async Task ShouldInvokeServerCallWithProxy()
{
//Arrange
var client = _api.StartWithProxy();
//Act
var val = await client.AddSalt("test");
//Assert
Assert.Equal("test-foo", val);
}
При использовании класса TestApi
, как и при использовании ApiClientTest
, в вывод теста отправляются дампы запросов и ответов. Для этого необходимо полю объекта TestApi.Output
(рекомендуется делать это в конструкторе класса теста) присвоить объекта тестового вывода ITestOutputHelper
. Пример вывода дампов для двух примеров выше (эквивалентно для обоих примеров):
===== REQUEST BEGIN (ITestApi) =====
GET http://localhost/test/add-salt
Cookie: <empty>
Content-Type: application/json
Content-Length: 6
"test"
===== REQUEST END =====
===== RESPONSE BEGIN (ITestApi) =====
200 OK
Content-Type: text/plain; charset=utf-8
test-foo
===== RESPONSE END =====
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp3.1 is compatible. |
-
.NETCoreApp 3.1
- Microsoft.AspNetCore.Mvc.Testing (>= 3.1.3)
- MyLab.ApiClient (>= 3.16.25)
- xunit.abstractions (>= 2.0.3)
-
net6.0
- Microsoft.AspNetCore.Mvc.Testing (>= 6.0.11)
- MyLab.ApiClient (>= 3.16.25)
- xunit.abstractions (>= 2.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.