【问题标题】:ASP .NET Core Integration Tests fails with corrupted bodyASP .NET Core 集成测试因主体损坏而失败
【发布时间】:2020-07-05 07:15:32
【问题描述】:

我正在开展一个项目,我们必须使用 ASP .NET Core 3.x 开发一个 Web API。到目前为止,一切顺利,运行良好。现在,我正在为这个 Web API 编写一些集成测试,但除了 GET 请求之外,我很难让测试正常工作。

我们正在使用 Jason Taylor 的 Clean Architecture。这意味着我们有一个包含所有请求处理程序的核心项目、一个包含所有数据库实体的域项目和一个用于 API 控制器的演示项目。我们使用MediatR 和依赖注入来进行这些项目之间的通信。

现在,我们遇到了reuqest的body数据没有到达控制器的问题。

控制器中的 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 错误。 如果我们在 Debug 模式下运行测试,我们可以看到代码由于模型状态错误而引发了自定义 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 in Debugging mode with json error.

在我读过的一篇 stackoverflow 文章中,删除 [ApiController] 类属性可能会导致更详细的错误描述。在再次调试期间,测试并在带有await Mediator.Send(command); 的行的FavoriteController 的Update 方法中设置断点,我可以看到,到达Update 方法的命令对象只包含空值或默认值,除了id,它是 5。

command 
    Caption         null    string
    FavoriteName    null    string
    Id              5       long
    Public          false   bool

最令人困惑(和令人沮丧)的部分是,使用 swagger 或 postman 的手动测试都成功了。按照我的理解,集成测试的时候肯定有问题。

我希望有人可以帮助我,看看我缺少什么。会不会是Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory的HttpClient有问题?

【问题讨论】:

    标签: c# asp.net-core integration-testing asp.net-core-webapi


    【解决方案1】:

    我们在 web api 演示项目的 LoggingMiddleware 中发现了问题。 在写这个问题之前,我们已经看过另一篇关于 stackoverflow 的文章: ASP.NET MVC Core 3.0 - Why API Request from body keeps returning !ModelState.IsValid? 但是我们的代码中已经有了request.Body.Seek(0, SeekOrigin.Begin);。所以,我们认为就是这样,这不可能是问题。

    但是现在,我们发现了这篇文章: .net core 3.0 logging middleware by 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);
    

    【讨论】:

      猜你喜欢
      • 2020-03-18
      • 2020-10-27
      • 2011-10-22
      • 1970-01-01
      • 2014-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多