ASP. NET Сбой тестирования интеграции ядра с поврежденным телом - PullRequest
0 голосов
/ 24 марта 2020

Я работаю над проектом, в котором мы должны разработать веб-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?

1 Ответ

0 голосов
/ 25 марта 2020

Мы обнаружили проблему в нашем LoggingMiddleware проекта веб-презентации API. Перед тем, как написать этот вопрос, мы уже заглядываем в другую статью о stackoverflow: ASP. NET MVC Core 3.0 - почему API-запрос от тела продолжает возвращаться! ModelState.IsValid? Но мы уже имел request.Body.Seek(0, SeekOrigin.Begin); в нашем коде. Итак, мы подумали, что это все, и это не могло быть проблемой.

Но теперь мы нашли эту статью: . net промежуточное ПО для ведения журнала ядра 3.0 от pipereader

Вместо того, чтобы читать тело запроса следующим образом:

await request.Body.ReadAsync(buffer, 0, buffer.Length);

... где поток закрыт после чтения, мы теперь используем BodyReader в качестве потока и оставляем поток открытым:

var stream = request.BodyReader.AsStream(true); // AsStream(true) to let stream open
await stream.ReadAsync(buffer, 0, buffer.Length);
request.Body.Seek(0, SeekOrigin.Begin);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...