Skip to content

feat: key-aware ClassDataSource via IKeyedClassDataSource interface #4764

@thomhurst

Description

@thomhurst

Problem

When using SharedType.Keyed with [ClassDataSource], the fixture instance has no way to know what key it was created with. This forces users to create separate subclasses for each key variant:

// Current: must create a subclass per key
public abstract class AuthenticatedApiClient : IAsyncInitializer
{
    protected abstract string UserEmail { get; }
    protected abstract string UserPassword { get; }
    protected abstract string UserRole { get; }
    // ... authentication logic
}

// Boilerplate subclass just to set credentials
public class AdminApiClient : AuthenticatedApiClient
{
    protected override string UserEmail => "admin@cloudshop.test";
    protected override string UserPassword => "Admin123!";
    protected override string UserRole => "admin";
}

// Another boilerplate subclass
public class CustomerApiClient : AuthenticatedApiClient
{
    protected override string UserEmail => "customer@cloudshop.test";
    protected override string UserPassword => "Customer123!";
    protected override string UserRole => "customer";
}

Proposed API

// New interface
public interface IKeyedClassDataSource
{
    string Key { set; } // TUnit sets this before InitializeAsync()
}

// Single class handles all roles
public class AuthenticatedApiClient : IAsyncInitializer, IKeyedClassDataSource
{
    public string Key { get; set; } = null!;

    public async Task InitializeAsync()
    {
        var (email, password) = Key switch
        {
            "admin" => ("admin@test.com", "Admin123!"),
            "customer" => ("customer@test.com", "Customer123!"),
            _ => throw new ArgumentException($"Unknown key: {Key}")
        };
        // authenticate with email/password...
    }
}

// Usage: one class, different keys
[ClassDataSource<AuthenticatedApiClient>(Shared = SharedType.Keyed, Key = "admin")]
public required AuthenticatedApiClient Admin { get; init; }

[ClassDataSource<AuthenticatedApiClient>(Shared = SharedType.Keyed, Key = "customer")]
public required AuthenticatedApiClient Customer { get; init; }

Benefits

  • Eliminates boilerplate subclasses for parameterized fixtures
  • Makes the Keyed sharing pattern much more useful
  • Single fixture class can serve many configurations
  • Key is visible at the usage site, improving readability

Context

Discovered while building the CloudShop Aspire + TUnit example (#4761). Had to create AdminApiClient and CustomerApiClient as separate classes when a single key-aware class would have sufficed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions