question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

The HTTP response obtained during functional tests with TestServer it doesn't always contain the originated request content

See original GitHub issue

Describe 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 an action, 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 and Map 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:closed
  • Created 4 years ago
  • Comments:10 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
mkArtakMSFTcommented, Dec 9, 2019

Thanks for contacting us. @javiercn can you please look into this? Thanks!

1reaction
adrianiftodecommented, Jun 24, 2020

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

FailedTest1

Read more comments on GitHub >

github_iconTop Results From Across the Web

TestServer response does not contain Transfer-encoding ...
Describe the bug While using the TestServer and the test http client, the Transfer-encoding header with the chunked value is missing.
Read more >
In-Memory ASP.NET Core Integration Tests with TestServer
The TestServer class allows an in-memory test server to be created and HTTP requests issued to the ASP.NET Core Web app.
Read more >
Integration testing - Apollo GraphQL Docs
Apollo Server uses a multi-step request pipeline to validate and execute incoming GraphQL operations. This pipeline supports integration with custom plugins ...
Read more >
Using xUnit to Test your C# Code
Testing library. This class creates a TestServer instance; that is, an in-memory server responding to HTTP requests. The TestServer is created ...
Read more >
How to test ASP.NET Core Minimal APIs
Send an HTTP request to the / endpoint; Verify the response. Earlier in this post, you have added a reference to the Microsoft.AspNetCore.Mvc....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found