1

Recently I wanted to upgrade from dot.net netcoreapp2.2 to netcoreapp3.1. I am using https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1 to execute tests for my API implementation.

There is one test that verifies that cancellation of the user is returning 499 status code (https://httpstatuses.com/499). The implementation of this feature is done via middleware.

In the test the critical assertion looks like this:

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var client = this.CreateHttpClient(repositoryMock);
HttpResponseMessage responseMessage = null;
await Task.WhenAll(
    Task.Run(async () =>
        {
            await Task.Delay(500);
            tokenSource.Cancel();
        }),
    Task.Run(async () =>
        {
            responseMessage = await client.GetAsync(new Uri($"http://localhost/api/values/haxi?haxiIds={haxiGuid}"), token);
        }));
Assert.That(
    responseMessage.StatusCode,
    Is.EqualTo((HttpStatusCode)499));

In 2.2 everything is working fine. The server cancels, returns 499 and the HttpClient receives it.

In 3.1 it looks like the server cancels, returns 499, but the HttpClient always throws an exception:

  Message: 
    System.OperationCanceledException : The operation was canceled.
  Stack Trace: 
    HttpClient.HandleFinishSendAsyncError(Exception e, CancellationTokenSource cts)
    HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
    <<Cancellation_ShouldReturn499>b__3>d.MoveNext() line 57
    --- End of stack trace from previous location where exception was thrown ---
    ControllerTests.Cancellation_ShouldReturn499() line 49
    GenericAdapter`1.BlockUntilCompleted()
    NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaitable)
    AsyncToSyncAdapter.Await(Func`1 invoke)
    TestMethodCommand.RunTestMethod(TestExecutionContext context)
    TestMethodCommand.Execute(TestExecutionContext context)
    SimpleWorkItem.PerformWork()

I have setup a complete new solution to reproduce the problem: https://github.com/schrufygroovy/assert-api-cancellation.

Is there some alternative way in 3.1 to verify the response of a user-cancelled http request? Or is my setup of the API in 3.1 wrong? Is the middleware executed, but then overruled somehow by some other new 3.1 feature?

grafbumsdi
  • 405
  • 3
  • 11

1 Answers1

0

Looks like the behaviour of the HttpClient changed from 2.2 to 3.1

I was able to resolve the problem by using custom HttpMessageInvoker instead of HttpClient.

Make some function to create a HttpMessageInvoker that is using the HttpMessageHandler of the TestServer (using WebApplicationFactory with WithWebHostBuilder):

private HttpMessageInvoker CreateHttpMessageInvoker(Mock<IHaxiRepository> repositoryMock)
{
    return new HttpMessageInvoker(this.factory.WithWebHostBuilder(
        builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped(typeof(IHaxiRepository), provider => repositoryMock.Object);
            });
        }).Server.CreateHandler(), true);
}

Use that one instead of HttpClient:

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
using var httpMessageInvoker = this.CreateHttpMessageInvoker(repositoryMock);
HttpResponseMessage responseMessage = null;
await Task.WhenAll(
    Task.Run(async () =>
        {
            await Task.Delay(500);
            tokenSource.Cancel();
        }),
    Task.Run(async () =>
        {
            responseMessage = await httpMessageInvoker.SendAsync(
                new HttpRequestMessage(HttpMethod.Get, new Uri($"http://localhost/api/values/haxi?haxiIds={haxiGuid}")),
                token);
        }));
Assert.That(
    responseMessage.StatusCode,
    Is.EqualTo((HttpStatusCode)499));
grafbumsdi
  • 405
  • 3
  • 11