#c# #asp.net-core #testing #integration-testing #xunit
#c# #asp.net-core #тестирование #интеграция-тестирование #xunit
Вопрос:
я новичок в интеграционных тестах. У меня есть метод контроллера, который добавляет пользователя в базу данных, как показано ниже:
[HttpPost]
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserRequest request)
{
try
{
var command = new CreateUserCommand
{
Login = request.Login,
Password = request.Password,
FirstName = request.FirstName,
LastName = request.LastName,
MailAddress = request.MailAddress,
TokenOwnerInformation = User
};
await CommandBus.SendAsync(command);
return Ok();
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
Как вы заметили, мой метод не возвращает никакой информации о пользователе, который был добавлен в базу данных — он информирует о результатах обработки определенного запроса с использованием кодов состояния. Я написал интеграционный тест, чтобы проверить, работает ли он правильно:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "Aleksander",
LastName = "Kowalski",
MailAddress = "akowalski@onet.poczta.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
Я не уверен, достаточно ли утверждать только код состояния ответа, возвращаемый с сервера. Я в замешательстве, потому что, я не знаю, должен ли я прикрепить к коду раздела assert, который получит всех пользователей и проверит, содержит ли он, например, созданного пользователя. У меня даже нет идентификатора такого пользователя, потому что мое приложение находит новый идентификатор для пользователя при добавлении его / ее в базу данных. Я также понятия не имею, как тестировать подобные методы:
[HttpGet("{userId:int}")]
public async Task<IActionResult> GetUserAsync([FromRoute] int userId)
{
try
{
var query = new GetUserQuery
{
UserId = userId,
TokenOwnerInformation = User
};
var user = await QueryBus
.SendAsync<GetUserQuery, UserDto>(query);
var result = user is null
? (IActionResult) NotFound(new
{
Message = (string) _stringLocalizer[UserConstants.UserNotFoundMessageKey]
})
: Ok(user);
return resu<
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
Я считаю, что я должен каким-то образом сначала создать пользователя в разделе Arrange, получить его идентификатор, а затем использовать его в разделе Act с помощью метода GetUserAsync, вызываемого с запросом, отправленным HttpClient. Снова та же проблема — после создания не возвращается информация о пользователе (кстати, она не возвращается из-за моего дизайна CQRS во всем приложении — команды не возвращают никакой информации). Не могли бы вы объяснить мне, как правильно написать такие тесты? Я что-нибудь пропустил? Спасибо за любую помощь.
Комментарии:
1. Это просто: вы не можете написать интеграционный тест, который утверждает, что за пределами HTTP200, это природа асинхронного приложения. Если вам нужны дополнительные утверждения, вам понадобятся автоматические / сквозные тесты, в которых вы создаете случайного пользователя и ждете (повторяя попытку динамически), пока система не сможет создать пользователя в реальной базе данных. В качестве теста уровня интеграции, который можно запускать как часть сборки, ваш код выглядит нормально, просто утверждая, что ответ в порядке
Ответ №1:
Вот как я это делаю:
var response = (CreatedResult) await _controller.Post(createUserRequest);
response.StatusCode.Should().Be(StatusCodes.Status201Created);
Вторая строка выше не нужна, просто для иллюстрации.
Кроме того, ваш ответ будет лучше, если вы вернете 201 (созданный) вместо 200 (OK) для Post-глаголов, например:
return Created($"api/users/{user.id}", user);
Для тестирования notfound’s:
var result = (NotFoundObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status404NotFound);
NotFoundObjectResult
Предполагается, что вы что-то возвращаете. Если вы просто отвечаете 404 и без объяснения причин, замените NotFoundObjectResult
на NotFoundResult
.
И, наконец, InternalServerErrors:
var result = (ObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
Ответ №2:
Для этого вы можете использовать integrationFixture, используя этот пакет NuGet . Это альтернатива автоматической фиксации для интеграционных тестов.
В документированных примерах используются вызовы Get, но вы можете выполнять и другие вызовы. По логике вещей, вы должны проверить код состояния ( OkObjectResult
означает 200) и ответ (который может быть пустой строкой, это вообще не проблема).
Вот документированный пример обычного вызова Get.
[Fact]
public async Task GetTest()
{
// arrange
using (var fixture = new Fixture<Startup>())
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var controller = fixture.Create<SearchEngineController>();
// act
var response = await controller.GetNumberOfCharacters("Hoi");
// assert
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
Assert.Equal(8, ((OkObjectResult)response.Result).Value);
}
}
}
private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
fluentMockServer.Given(Request.Create().UsingGet())
.RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
.WithStatusCode(HttpStatusCode.OK));
}
В приведенном выше примере контроллер решается с помощью DI, описанного в вашем Startup
классе.
Вы также можете выполнить фактический вызов REST, используя using Refit . Приложение самостоятельно размещается внутри вашего теста.
using (var fixture = new RefitFixture<Startup, ISearchEngine>(RestService.For<ISearchEngine>))
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var refitClient = fixture.GetRefitClient();
var response = await refitClient.GetNumberOfCharacters("Hoi");
await response.EnsureSuccessStatusCodeAsync();
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
}
}