testfx icon indicating copy to clipboard operation
testfx copied to clipboard

ClassCleanupBehavior.EndOfClass causes Microsoft.AspNetCore.Mvc.Testing based tests to fail with TaskCanceledException: The operation was canceled.

Open abatishchev opened this issue 1 year ago • 3 comments

Describe the bug

Test which use an in-memory ASP.NET Core web server and linked HTTP client started failing with the following exception after I replaced [ClassCleanup] with [ClassCleanupBehavior.EndOfClass] per MSTEST0034:

Failed DeleteAccount_Should_Return_Ok [135 ms]
  Error Message:
   Test method Provisioning.Service.Tests.Functional.Controllers.DataControllerTests.DeleteAccount_Should_Return_Ok threw exception: 
System.Threading.Tasks.TaskCanceledException: The operation was canceled. ---> System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException:  ---> System.IO.IOException: The client aborted the request.
  Stack Trace:
      at Microsoft.AspNetCore.TestHost.ResponseBodyReaderStream.CheckAborted()
   at Microsoft.AspNetCore.TestHost.ResponseBodyReaderStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
   at System.IO.Stream.<CopyToAsync>g__Core|27_0(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
   at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer)
--- End of inner exception stack trace ---
    at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
--- End of inner exception stack trace ---
    at System.Net.Http.HttpClient.HandleFailure(Exception e, Boolean telemetryStarted, HttpResponseMessage response, CancellationTokenSource cts, CancellationToken cancellationToken, CancellationTokenSource pendingRequestsCts)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Provisioning.Service.Tests.Functional.Controllers.DataControllerTests.SendAsync[T](HttpMethod httpMethod, Uri url, T resource) in C:\__w\1\s\src\Provisioning\Service.Tests.Functional\Controllers\DataControllerTests.cs:line 103
   at Provisioning.Service.Tests.Functional.Controllers.DataControllerTests.DeleteAccount_Should_Return_Ok() in C:\__w\1\s\src\Provisioning\Service.Tests.Functional\Controllers\DataControllerTests.cs:line 45

Steps To Reproduce

[TestClass]
public class DataControllerTests
{
    protected static WebApplicationFactory<Program> _app;
    protected static HttpClient _httpClient;

    [ClassInitialize]
    public static void ClassInitialize(TestContext context)
    {
        _app = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder => builder.ConfigureServices(s =>
            {
                // setting up some mocks
            })
            .UseEnvironment(Environments.Development));
        _httpClient = _app.CreateDefaultClient();
    }

    [ClassCleanup(ClassCleanupBehavior.EndOfClass)] // <-- !
    public static void ClassCleanup() =>
        _app.Dispose();

    [TestMethod]
    public async Task DeleteAccount_Should_Return_Ok()
    {
        var account = CreateAccount();
        var url = new Uri($"/data/accounts/{account.Name}", UriKind.Relative);

        // Act
        using var response = await SendAsync(HttpMethod.Delete, url, account);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    private static Account CreateAccount() =>
        new Account(); // simple POCO

    private async Task<HttpResponseMessage> SendAsync<T>(
        HttpMethod httpMethod,
        Uri url,
        T resource)
        where T : class
    {
        var request = new HttpRequestMessage(httpMethod, url)
        {
            Content = new JsonContent(resource)
        };
        return await _httpClient.SendAsync(request, this.TestContext!.CancellationTokenSource.Token);
    }
}

Expected behavior

The tests continue working as before.

Actual behavior

See above.

Additional context

Test project:

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <Platform>AnyCPU</Platform>
  <IsPackable>false</IsPackable>
  <IsTestProject>true</IsTestProject>
</PropertyGroup>

NuGet packages:

<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.11" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="MSTest.TestAdapter" Version="3.7.0" />
<PackageVersion Include="MSTest.TestFramework" Version="3.7.0" />

<GlobalPackageReference Include="MSTest.Analyzers" Version="3.7.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</GlobalPackageReference>

abatishchev avatar Jan 01 '25 20:01 abatishchev

Hi @abatishchev,

Could you please share a full example? From the partial code, I am not able to reproduce the issue so I am probably not setuping things the same way you do. For example, are you using MSTest runner or still VSTest?

Evangelink avatar Jan 07 '25 13:01 Evangelink

Let me add an important detail (which however makes it harder to investigate the issue): the errors occur exclusively in an ADO pipeline (I shared a link with you privately).

abatishchev avatar Jan 09 '25 17:01 abatishchev

Also let me attach a better, fully working (compiling, but not failing) code: TestProject8.zip

abatishchev avatar Jan 09 '25 17:01 abatishchev

Closing as there is not enough info to reproduce and investigate the issue.

Youssef1313 avatar Sep 23 '25 07:09 Youssef1313