Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Aug 16, 2025

When using DependencyInjectionDataSourceAttribute<TScope> with multiple test arguments, the same dependency injection scope was incorrectly shared across all test invocations, causing scoped services to be reused when they should be isolated per test.

Problem

Consider this test class using dependency injection:

public sealed class Dependency
{
    private static int _value = 0;
    public int Value { get; } = Interlocked.Increment(ref _value);
}

public sealed class DependencyInjectionClassConstructor : DependencyInjectionDataSourceAttribute<IServiceScope>
{
    private readonly IServiceProvider _serviceProvider = BuildServiceProvider();

    private static IServiceProvider BuildServiceProvider()
    {
        var services = new ServiceCollection();
        services.AddScoped<Dependency>();
        return services.BuildServiceProvider();
    }

    public override object? Create(IServiceScope scope, Type type) =>
        ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, type);

    public override IServiceScope CreateScope(DataGeneratorMetadata dataGeneratorMetadata) =>
        _serviceProvider.CreateAsyncScope();
}

[DependencyInjectionClassConstructor]
public sealed class ActualTestClass(Dependency dependency)
{
    [Test]
    [Arguments(1)]
    [Arguments(2)]
    public void Test1(int testNumber)
    {
        Console.WriteLine(dependency.Value);
    }
}

Before this fix: Both test executions would output 1 because they shared the same DI scope and got the same Dependency instance.

After this fix: The test executions output different values (1 and 2 or 2 and 1) because each gets its own DI scope with a fresh Dependency instance.

Solution

The issue was in DependencyInjectionDataSourceAttribute<TScope>.GenerateDataSources() where the scope was created once during data generation and reused across all test executions:

// Before: Scope created once and shared
var scope = CreateScope(dataGeneratorMetadata);
yield return () => { /* all tests use same scope */ };

Changed to create a new scope for each test execution:

// After: Scope created per test execution
yield return () => {
    var scope = CreateScope(dataGeneratorMetadata); // Fresh scope per test
    // ... proper disposal setup for this specific scope
    // ... return test instance data
};

This ensures proper test isolation while maintaining backward compatibility. Each scope is properly disposed after its corresponding test execution through the test context's disposal events.

Fixes #2930.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits August 16, 2025 20:47
Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] Dependency Injection Scope shared between tests Fix dependency injection scope sharing between test executions Aug 16, 2025
@Copilot Copilot AI requested a review from thomhurst August 16, 2025 20:58
Copilot finished work on behalf of thomhurst August 16, 2025 20:58
…nflicts

Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
Copilot finished work on behalf of thomhurst August 16, 2025 21:17
Repository owner deleted a comment from Copilot AI Aug 16, 2025
@thomhurst thomhurst marked this pull request as ready for review August 16, 2025 22:22
@Copilot Copilot AI temporarily deployed to Pull Requests August 16, 2025 22:22 Inactive
@Copilot Copilot AI temporarily deployed to Pull Requests August 16, 2025 22:22 Inactive
@Copilot Copilot AI temporarily deployed to Pull Requests August 16, 2025 22:22 Inactive
@thomhurst thomhurst enabled auto-merge (squash) August 16, 2025 22:22
@thomhurst
Copy link
Owner

@copilot Add a test for this, and decorate it with [EngineTest(ExpectedResult.Pass)] so it is run as part of the regression

Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
auto-merge was automatically disabled August 16, 2025 22:38

Head branch was pushed to by a user without write access

Copy link
Contributor Author

Copilot AI commented Aug 16, 2025

@copilot Add a test for this, and decorate it with [EngineTest(ExpectedResult.Pass)] so it is run as part of the regression

Added regression test DependencyInjectionScopeIsolationTest decorated with [EngineTest(ExpectedResult.Pass)]. The test verifies that each test execution with multiple arguments gets its own DI scope and scoped services are not shared between invocations. It uses a ScopedTestService with unique instance numbers to verify isolation and includes validation that all instances are unique. Commit bb48bfc.

Copilot finished work on behalf of thomhurst August 16, 2025 22:39
@Copilot Copilot AI temporarily deployed to Pull Requests August 16, 2025 22:46 Inactive
@Copilot Copilot AI temporarily deployed to Pull Requests August 16, 2025 22:46 Inactive
@Copilot Copilot AI temporarily deployed to Pull Requests August 16, 2025 22:46 Inactive
@thomhurst thomhurst enabled auto-merge (squash) August 16, 2025 22:46
@thomhurst thomhurst merged commit 95ea966 into main Aug 16, 2025
8 of 9 checks passed
This was referenced Aug 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dependency Injection Scope shared between tests
2 participants