День 1519. #Testing
Как Тестировать Контракты HTTP API в .NET
Тесты HTTP API можно называть модульными, интеграционными или компонентными – это не так важно. Важно, чтобы тесты взаимодействовали только с интерфейсом, использующимся в производственном коде.
Если определённая часть вашей системы вызывается только через HTTP API, тест должен делать то же самое. Прямой вызов метода класса контроллера ASP.NET нарушает эту идею.
Убедитесь, что вы проверяете только то, что имеет отношение к конкретному тестовому случаю:
- Если вы ожидаете исключения, убедитесь, что тип исключения правильный, свойства имеют правильные значения, а сообщение соответствует ожиданиям.
- В сообщении об исключении, часто надо проверить только отдельные части. Утверждение WithMessage в Fluent Assertions принимает шаблон сообщения именно по этой причине.
- Если API возвращает конкретный код ошибки HTTP, проверяйте только его и игнорируйте тело.
- Если тест охватывает определённый URL, где имеет значение только определённое свойство результата, игнорируйте остальные.
Это позволяет избегать провалов тестов по несвязанным причинам.
Многие разработчики используют в тестах реальный тип (например, тип DTO) из кода проекта, чтобы сравнивать с ним десериализованный результат, полученный из HTTP API. Обычный аргумент в пользу этого: это удобнее для рефакторинга, так что, например, изменение имени свойства этого типа не нарушит теста.
Но на самом деле это должно ломать тест! Маршрут, заголовки и конкретный JSON, возвращаемый HTTP API, являются контрактом и, следовательно, должны рассматриваться как таковые.
Как быть? Есть два распространённых способа:
- Использовать необработанный JSON. Самый чистый способ, но это сложно, если надо проверить только отдельные части результата.
- Десериализовать результат в анонимный тип определённой структуры. Рассмотрим пример, используя NewtonSoft.Json и Fluent Assertions:
IHost host = GetTestClient();
var response = await host.GetAsync(…);
var body = await
response.Content.ReadAsStringAsync();
var expect = new[] {
new {
State = "Active",
Count = 1
}
}
var actual = JsonConvert
.DeserializeAnonymousType(body, expect);
actual.Should().BeEquivalentTo(expect);
Здесь мы устанавливаем ожидание (expect) с определёнными значениями, а затем используем DeserializeAnonymousType, чтобы NewtowSoft.Json попытался десериализовать JSON в анонимный объект, структура которого определяется объектом expect. BeEquivalentTo использует глубокое сравнение ожидаемого и фактического (actual) объектов.
В System.Text.Json мы можем добиться того же результата:
…
var actual = JsonSerializer.Deserialize(
body,
expect.GetType(),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
actual.Should().BeEquivalentTo(expect);
Вы можете инкапсулировать большую часть логики проверки в метод BeEquivalentTo, который принимает множество настроек. Также можно использовать метод Should().BeAs(), предоставляемый библиотекой FluentAssertions.Web.
Источник: https://www.continuousimprover.com/2023/03/test-http-contracts.html