Я работаю над проектом, в котором мы должны разработать веб-API с ASP. NET Core 3.x. Пока все хорошо, работает хорошо. Сейчас я пишу несколько интеграционных тестов для этого веб-API, и у меня возникли проблемы с получением тестов для всего, кроме запроса GET, для работы.
Мы используем чистую архитектуру от Джейсона Тейлора. Это означает, что у нас есть основной проект со всем нашим обработчиком запросов, проект домена со всеми объектами базы данных и проект презентации для контроллеров API. Мы используем MediatR и внедрение зависимостей для связи между этими проектами.
Теперь у нас проблема в том, что данные тела запроса не достигают контроллера.
Вот как выглядит метод Update в контроллере:
[ApiController]
[Route("api/[controller]/[action]")]
public abstract class BaseController : ControllerBase
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
}
public class FavoriteController : BaseController
{
[HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(long id, UpdateFavoriteCommand command)
{
if (command == null || id != command.Id)
{
return BadRequest();
}
// Sends the request to the corresponding IRequestHandler
await Mediator.Send(command);
return NoContent();
}
}
Мы используем xUnit. net в качестве тестовой среды. Для интеграционных тестов мы используем базу данных InMemory SQLite, настроенную в классе фикстур.
Тест выглядит следующим образом:
public class UpdateFavoritesTestSqlite : IClassFixture<WebApplicationFactoryWithInMemorySqlite<Startup>>
{
private readonly WebApplicationFactoryWithInMemorySqlite<Startup> _factory;
private readonly string _endpoint;
public UpdateFavoritesTestSqlite(WebApplicationFactoryWithInMemorySqlite<Startup> factory)
{
_factory = factory;
_endpoint = "api/Favorite/Update/";
}
[Fact]
public async Task UpdateFavoriteDetail_WithFullUpdate_ShouldUpdateCorrectly()
{
// Arange
var client = _factory.CreateClient(); // WebApplicationFactory.CreateClient()
var command = new UpdateFavoriteCommand
{
Id = 5,
Caption = "caption new",
FavoriteName = "a new name",
Public = true
};
// Convert to JSON
var jsonString = JsonConvert.SerializeObject(command);
var httpContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
var stringUri = client.BaseAddress + _endpoint + command.Id;
var uri = new Uri(stringUri);
// Act
var response = await client.PutAsync(uri, httpContent);
response.EnsureSuccessStatusCode();
httpContent.Dispose();
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.NoContent);
}
}
Если мы запустим тест, мы получите ошибку 400 Bad Request. Если мы запустим тест в режиме отладки, то увидим, что код генерирует специальное исключение ValidationException из-за ошибки состояния модели. Это настраивается в DependencyInjection проекта презентации:
services
.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var failures = context.ModelState.Keys
.Where(k => ModelValidationState.Invalid.Equals(context.ModelState[k].ValidationState))
.ToDictionary(k => k, k => (IEnumerable<string>)context.ModelState[k].Errors.Select(e => e.ErrorMessage).ToList());
throw new ValidationException(failures);
};
})
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<IWebApiDbContext>());
Объект сбоев содержит одну ошибку, которая говорит:
The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
Вот снимок экрана: Visual Studio в отладке режим с ошибкой json.
В одной статье в stackoverflow я прочитал, что удаление атрибута класса [ApiController]
может привести к более подробному описанию ошибки. Во время повторной отладки теста и установки точки останова в методе Update из FavoriteController в строке с await Mediator.Send(command);
я смог увидеть, что объект команды, получающий метод Update, содержит только нулевые значения или значения по умолчанию, кроме идентификатора, который было 5.
command
Caption null string
FavoriteName null string
Id 5 long
Public false bool
Самая запутанная (и разочаровывающая) часть заключается в том, что оба ручных теста с чванством или почтальоном успешны. Насколько я понимаю, во время интеграционного теста должна быть проблема.
Надеюсь, кто-то может мне помочь и посмотреть, что мне не хватает. Возможно ли, что что-то не так с HttpClient в Microsoft.AspNetCore. Mvc .Testing.WebApplicationFactory?