microsoft / microsoft/FeatureManagement-Dotnet

Run tests in parallel using WebApplicationFactory throw exception

Open
#577 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
1.2k
Forks
129
Avg merge
1d 1h
Merged PRs (30d)
5

Description

In api project i use Microsoft.FeatureManagement library and it works until i want to run tests in parallel.

to fix the problem i tried to put lock and it works, but it's the same that run then tests sequentially

    // Static lock to prevent concurrent host/server initialization across factory instances
    // This prevents race conditions in service registration (e.g., AddFeatureManagement)
    // while still allowing each test class to have its own factory instance
    private static readonly object _hostCreationLock = new object();


    /// <summary>
    /// Override CreateHost to synchronize host creation across parallel factory instances.
    /// This ensures service registration (including AddFeatureManagement) happens sequentially,
    /// preventing race conditions while still allowing parallel test execution.
    /// </summary>
    protected override IHost CreateHost(IHostBuilder builder)
    {
        lock (_hostCreationLock)
        {
            return base.CreateHost(builder);
        }
    }
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class CustomWebApplicationFactory : WebApplicationFactory<AssemblyMarker>
{

    private HttpMessageHandler? _optiConfigHttpMessageHandler;

    public void SetOptiConfigHttpMessageHandler(HttpMessageHandler handler)
    {
        _optiConfigHttpMessageHandler = handler;
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Configure logging for tests
            services.AddLogging(logging =>
            {
                logging.ClearProviders();
                logging.AddSimpleConsole(options =>
                {
                    options.SingleLine = true;
                    options.TimestampFormat = "HH:mm:ss.fff ";
                    options.UseUtcTimestamp = true;
                    options.IncludeScopes = false;
                });
                logging.SetMinimumLevel(LogLevel.Debug);
            });
        });
    }
    
}


using Api.Private.IntegrationTests.Infrastructure;
using Identity.Tests.Shared.Mocking.ExternalIdentityProvider;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;

namespace Api.Private.IntegrationTests.GraphQL.Dashboards.OptiLeads;

public class GetSalesRealisedTests : IClassFixture<CustomWebApplicationFactory>, IDisposable
{
    private readonly IServiceScope _scope;

    private readonly IPrivateClient _client;

    private readonly HttpClient _httpClient;

    private readonly ITestContextAccessor _testContextAccessor;

    public GetSalesRealisedTests(
        CustomWebApplicationFactory factory,
        ITestContextAccessor testContextAccessor)
    {
        _testContextAccessor = testContextAccessor;

        // Create a scope from the factory's services
        _scope = factory.Services.CreateScope();

        // Configure the test HttpClient with the correct BaseAddress
        _httpClient = factory.CreateClient();
        _httpClient.BaseAddress =
            new Uri(_httpClient.BaseAddress ?? new Uri("http://localhost"), "/graphql-private");
        factory.Server.PreserveExecutionContext = true;

        // Register the TestHttpClientFactory in the scope
        var services = new ServiceCollection();
        services.AddSingleton<IHttpClientFactory>(_ => new TestHttpClientFactory(_httpClient));
        services.AddPrivateClient();

        var serviceProvider = services.BuildServiceProvider();
        _client = serviceProvider.GetRequiredService<IPrivateClient>();
    }

    [Fact]
    public async Task GetSalesRealised_ReturnsDataForUserWithPermissions()
    {
        
    }

    public void Dispose()
    {
        _scope.Dispose();
        _httpClient.Dispose();
    }
}

at System.Collections.Generic.List`1.Enumerator.MoveNext()
at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
at Microsoft.FeatureManagement.FeatureManagementBuilder.AddFeatureFilter[T]()
at Microsoft.FeatureManagement.ServiceCollectionExtensions.GetFeatureManagementBuilder(IServiceCollection services)
at Microsoft.FeatureManagement.ServiceCollectionExtensions.AddFeatureManagement(IServiceCollection services)
at API.CompositionRoot.addFeatureManagements(IServiceCollection services) in /Users/admin/Works/NewOne/backend/src/API/CompositionRoot.fs:line 373
at API.CompositionRoot.compose(IConfiguration cfg, IServiceCollection services) in /Users/admin/Works/NewOne/backend/src/API/CompositionRoot.fs:line 399
at <StartupCode$API>.$Program.main@() in /Users/admin/Works/NewOne/backend/src/API/Program.fs:line 43
at InvokeStub_$Program.main@(Object, Object, IntPtr*)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing parallel integration-test execution with CustomWebApplicationFactory and inspect the FeatureManagement stack trace at API.CompositionRoot.fs lines 373 and 399 and API/Program.fs line 43. Compare host creation and service registration across the shown test fixture, then verify that the tests run in parallel without the exception and without requiring sequential locking.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, fsharp
Domain
backend, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.