Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collection Fixture Support #673

Closed
DDDustinZ opened this issue May 4, 2024 · 5 comments
Closed

Collection Fixture Support #673

DDDustinZ opened this issue May 4, 2024 · 5 comments
Labels
enhancement New feature or request implemented requested feature has been implemented

Comments

@DDDustinZ
Copy link

DDDustinZ commented May 4, 2024

Hi,

New to FE here, but I was curious if we could get some support for Collection Fixtures. I was hoping to have something with a similar contract to TestBase with Setup/TearDown and Faker instance but not have to create that myself.

The problem

I'd like to set up and open a DB connection before any integration tests run and close it when they've all completed (for use with Respawn).

What I tried

PreSetupAsync on the AppFixture works ok to set up the connection but since it's invoked before the WAF, I can't be lazy and use it to get my connection string. Regardless I didn't see any way to TearDown once after all tests have completed. If possible to add, that would be a fine solution, but I didn't see any easy ways for FE to implement that.

Possible Addition

A new test base class that doesn't implement IClassFixture.

public abstract class TestCollectionBase<TAppFixture, TCollection> : IAsyncLifetime, IFaker 
    where TAppFixture : BaseFixture 
    // not used but helps drive the consumer to know they need to create a collection fixture
    where TCollection : ICollectionFixture<TAppFixture>
{
    static readonly Faker _faker = new();

    public Faker Fake => _faker;
    
    protected virtual Task SetupAsync() => Task.CompletedTask;
    
    protected virtual Task TearDownAsync() => Task.CompletedTask;

    Task IAsyncLifetime.InitializeAsync() => SetupAsync();

    Task IAsyncLifetime.DisposeAsync() => TearDownAsync();
}

Usage

public class IntegrationTestFixture : AppFixture<Program>
{    
    protected override async Task SetupAsync()
    {
        // Open Db connection
    }

    public async Task ResetDb()
    {
        // Reset Db with connection
    }

    protected override async Task TearDownAsync()
    {
        //Close Db connection
    }
}

[CollectionDefinition(nameof(IntegrationDbCollection))]
public class IntegrationDbCollection : ICollectionFixture<IntegrationTestFixture>
{
}

[Collection(nameof(IntegrationDbCollection))]
public abstract class IntegrationDbTestBase(IntegrationTestFixture fixture)
    : TestCollectionBase<IntegrationTestFixture, IntegrationDbCollection>
{
    protected override async Task SetupAsync()
    {
        await fixture.ResetDb();
    }
}

public class RepositoryTests(IntegrationTestFixture fixture) : IntegrationDbTestBase(fixture)
{
    [Fact]
    public void Test()
    {
        //Db reset before getting here
    }
}
@dj-nitehawk dj-nitehawk added enhancement New feature or request pending investigation this issue is queued up to be investigted labels May 5, 2024
@dj-nitehawk
Copy link
Member

not a fan of collection fixtures due to two reasons:

  • we lose the ability to run test-classes in parallel when classes are grouped into a test-collection.
  • too verbose to set things up with multiple classes/attributes etc.

so, we won't be adding any support for collection-fixtures in the FastEndpoints.Testing pkg.

however, i believe for your scenario, an AssemblyFixture would be a good fit. i've added our own (more convenient) version of assembly-fixture support in v5.25.0.2-beta.

here's how to use it:

  1. upgrade to v5.25.0.2-beta
  2. add an assembly attribute somewhere in the test project.
// somefile.cs
[assembly: EnableAssemblyFixtures]
  1. create a global app fixture
//same old AppFixture base but, lifecycle of the fixture becomes different
//turns into a singleton for the whole test run.

public class GlobalApp : AppFixture<Program>
{
    protected override async Task PreSetupAsync()
    {
        //triggered once for the whole test run
        //before any test-classes are executed
    }

    protected override async Task SetupAsync()
    {
        //triggered once for the whole test run
        //after the WAF has been instantiated
    }

    protected override async Task TearDownAsync()
    {
        //triggered once for the whole test run
        //when all test-classes have been executed
    }
}
  1. create test-classes by inheriting from TestBaseWithAssemblyFixture<GlobalApp>
//all test-classes receive the same derived GlobalApp instance

public class TestClassA(GlobalApp App) : TestBaseWithAssemblyFixture<GlobalApp>
{
    
}

public class TestClassB(GlobalApp App) : TestBaseWithAssemblyFixture<GlobalApp>
{
    
}

that's it...

give it a whirl and let me know how it goes.

i'm not familiar with respawn. so if the above doesn't seem to work with respawn, could you possibly provide a repro project i can debug and see what's going on?

thanks!

@dj-nitehawk dj-nitehawk added implemented requested feature has been implemented wip work is in progress and removed pending investigation this issue is queued up to be investigted labels May 5, 2024
@DDDustinZ
Copy link
Author

Sorry I didn't explain the full problem. I need to be able to do 1 time setup/teardown to manage the DB connection but I also need each test using the fixture to reset the DB to a known state before the test execution. Because of this, it's required that they also run serially so they don't step on each other.

Assembly fixture works great for the 1 time setup/teardown but (if I'm reading the doc correctly) the tests will run in parallel unless I disable parallelization for the whole assembly (would prefer not).

I'll still give it a try and report back but I'm guessing this can only be solved with a custom collection.

@dj-nitehawk dj-nitehawk added pending investigation this issue is queued up to be investigted and removed implemented requested feature has been implemented wip work is in progress labels May 6, 2024
dj-nitehawk added a commit that referenced this issue May 6, 2024
@dj-nitehawk
Copy link
Member

okie i think i've got it with v5.25.0.3-beta.
this is the cleanest approach i could come up with, which is still inline with how xunit does collection fixtures.

[CollectionDefinition(Name)]
public class MyTestCollection : TestCollection<MyAppFixture>
{
    public const string Name = nameof(MyTestCollection);
}

[Collection(MyTestCollection.Name)]
public class TestClassA(MyAppFixture App) : TestBase
{
    ...
}

[Collection(MyTestCollection.Name)]
public class TestClassB(MyAppFixture App) : TestBase
{
    ...
}

@dj-nitehawk dj-nitehawk added implemented requested feature has been implemented and removed pending investigation this issue is queued up to be investigted labels May 6, 2024
@DDDustinZ
Copy link
Author

I was just about to report back when I saw this new addition, gave it a try and it works perfectly. About as clean as you can get when working with collections, I love it!

I'm very quickly becoming a big fan of FE and of you, Thank you!

@dj-nitehawk
Copy link
Member

you're most welcome and thanks for the feedback!
v5.25.0.4-beta has been fortified with tests and you can use it in production if you wish.
v5.26 will be out with this during the first week of june.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request implemented requested feature has been implemented
Development

No branches or pull requests

2 participants