The HTTP response obtained during functional tests with TestServer it doesn't always contain the originated request content
See original GitHub issueDescribe the bug
There are some scenarios when the response.RequestMessage.Content does not contain the content used with the request that led to the response message while using the client created by the TestServer class.
So for example:
- when there are a
controller
and anaction
, no matter if the request is with an expected 200 OK or an expected 400 BadRequest, the response won’t contain the body used within the original request; the expected behavior is to contain it - if
UseEndpoints
andMap
are used for mapping and an expected 400 BadRequest is tried, then the response will contain this time the request; this is the expected behavior - if the request is an expected 404 BadRequet, then the response will contain the request’s content; also the expected behavior
To Reproduce
I’ve added a sample repository here, with tests included
I’ll copy here the code too
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace RequestMessageIssueTests
{
public sealed class Program
{
public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run();
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public static void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public class Comment
{
[Required]
public string Author { get; set; }
}
[Route("api/[controller]")]
[ApiController]
public class CommentsController : ControllerBase
{
[HttpPost]
public Comment Post([FromBody] Comment value) => value;
}
[Route("api/[controller]")]
[ApiController]
public class IssuesController : ControllerBase
{
}
public class RequestMessagesTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public RequestMessagesTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task GivenAnController_AnAction_And_AValidRequest_WhenAssertingTheRequestContent_ItShouldHaveAvailableTheContent()
{
using var client = _factory.CreateClient();
var response = await client.PostAsync("/api/comments", new StringContent(@"{
""author"": ""John""
}", Encoding.UTF8, "application/json"));
// as expected
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// fails, the HttpContent from the originated request doesn't contain the request body
var requestContent = await response.RequestMessage.Content.ReadAsStringAsync();
Assert.NotEmpty(requestContent);
}
[Fact]
public async Task Given_ARequest_WithoutACorrespondingMVCAction_WhenAssertingTheRequestContent_ItShouldHaveAvailableTheContent()
{
using var client = _factory.CreateClient();
var response = await client.PostAsync("/api/issues", new StringContent(@"{
""issue"": ""An issue""
}", Encoding.UTF8, "application/json"));
// as expected
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
// passes, the response contains the originated request content
var requestContent = await response.RequestMessage.Content.ReadAsStringAsync();
Assert.NotEmpty(requestContent);
}
[Fact]
public async Task GivenAnWebHostBuilderSetupAndA404Request_WhenAssertingTheRequestContent_ItShouldHaveAvailableTheContent()
{
var builder = new WebHostBuilder();
builder.ConfigureServices(services =>
{
services.AddRouting();
});
builder.Configure(app => app.UseRouting());
using var testServer = new TestServer(builder);
using var client = testServer.CreateClient();
using var response = await client.PostAsync("/endpoint", new StringContent("request body"));
// expected
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
// as expected, the response contains the originated request content
var requestContent = await response.RequestMessage.Content.ReadAsStringAsync();
Assert.NotEmpty(requestContent);
}
[Fact]
public async Task GivenAnController_AnAction_And_ABadRequestRequest_WhenAssertingTheRequestContent_ItShouldHaveAvailableTheContent()
{
using var client = _factory.CreateClient();
var response = await client.PostAsync("/api/comments", new StringContent(@"{
""comment"": ""some comment""
}", Encoding.UTF8, "application/json"));
// as expected
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
// fails, the HttpContent from the originated request doesn't contain the request body
var requestContent = await response.RequestMessage.Content.ReadAsStringAsync();
Assert.NotEmpty(requestContent);
}
[Fact]
public async Task GivenAnWebHostBuilderSetupAndA400BadRequest_WhenAssertingTheRequestContent_ItShouldHaveAvailableTheContent()
{
var builder = new WebHostBuilder();
builder.ConfigureServices(services =>
{
services.AddRouting();
});
builder.Configure(app => app.UseRouting()
.UseEndpoints(endpoints => endpoints.Map("/endpoint",
context =>
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Task.CompletedTask;
})));
using var testServer = new TestServer(builder);
using var client = testServer.CreateClient();
using var response = await client.PostAsync("/endpoint", new StringContent("request body"));
// expected
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
// as expected, the response contains the originated request content
var requestContent = await response.RequestMessage.Content.ReadAsStringAsync();
Assert.NotEmpty(requestContent);
}
}
}
csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.0.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
</Project>
Further technical details
- ASP.NET Core version 3.0 in this sample, but I found this on 2.2
- Include the output of
dotnet --info
.NET Core SDK (reflecting any global.json): Version: 3.1.100 Commit: cd82f021f4
Runtime Environment: OS Name: Windows OS Version: 10.0.17763 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\3.1.100\
Host (useful for support): Version: 3.1.0 Commit: 65f04fb6db
.NET Core SDKs installed: 2.1.202 [C:\Program Files\dotnet\sdk] 2.1.500 [C:\Program Files\dotnet\sdk] 2.1.502 [C:\Program Files\dotnet\sdk] 2.1.503 [C:\Program Files\dotnet\sdk] 2.1.504 [C:\Program Files\dotnet\sdk] 2.1.505 [C:\Program Files\dotnet\sdk] 2.1.507 [C:\Program Files\dotnet\sdk] 2.1.508 [C:\Program Files\dotnet\sdk] 2.1.509 [C:\Program Files\dotnet\sdk] 2.1.602 [C:\Program Files\dotnet\sdk] 2.2.101 [C:\Program Files\dotnet\sdk] 2.2.106 [C:\Program Files\dotnet\sdk] 2.2.108 [C:\Program Files\dotnet\sdk] 2.2.202 [C:\Program Files\dotnet\sdk] 2.2.203 [C:\Program Files\dotnet\sdk] 2.2.401 [C:\Program Files\dotnet\sdk] 3.0.101 [C:\Program Files\dotnet\sdk] 3.1.100 [C:\Program Files\dotnet\sdk]
.NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.0.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.0.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.0.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
To install additional .NET Core runtimes or SDKs: https://aka.ms/dotnet-download
- The IDE (VS / VS Code/ VS4Mac) you’re running on, and it’s version VS2019
Issue Analytics
- State:
- Created 4 years ago
- Comments:10 (2 by maintainers)
Top GitHub Comments
Thanks for contacting us. @javiercn can you please look into this? Thanks!
I see what you mean, however, this issue is about accessing the Request content via the
response.RequestMessage.Content
path. This doesn’t work, as described in the original comment on this thread.Let me give you an example of why I need this.
I built a Fluent Assertions extension, named FluentAssertions.Web so it can be used to build assertions and to inspect the HttpResponseMessage objects. If a test fails, in this case, because of a BadRequest, it would be really helpful to see the original request, to avoid debugging the test in certain scenarios (if the response does not contain enough information).
This worked for a while, as it can be seen in this print screen