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

Various Test Class Examples #1094

Open
thomhurst opened this issue Oct 29, 2024 · 9 comments
Open

Various Test Class Examples #1094

thomhurst opened this issue Oct 29, 2024 · 9 comments
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers help wanted Extra attention is needed

Comments

@thomhurst
Copy link
Owner

Tests vary far and wide and can be vastly different from one another.

TUnit tries to be flexible in meeting different testing needs.

I'd find it really helpful to gather examples of test class examples and how you went about it.

For instance, testing an API against an in memory server. Did you inject in the server with a ClassDataSource and use an IAsyncInitializer? Or did you just statically set one up using a [Before(TestSession)] hook?

All of this would be useful, and we could create a section on the docs site dedicated to examples of how tests could be set up for particular scenarios.

@thomhurst thomhurst added documentation Improvements or additions to documentation help wanted Extra attention is needed good first issue Good for newcomers labels Oct 29, 2024
@viceroypenguin
Copy link
Contributor

Here's what I'm doing: https://github.com/viceroypenguin/VsaTemplate/tree/master/Web.Tests

@jacob7395
Copy link

jacob7395 commented Nov 19, 2024

Not got to the point of testing my test application using a test web application, specific I will be testing a signal R hub, but here is how I have setup my [ClassConstructor<DependencyInjectionClassConstructor>] to build my dependencies. I added a simple attribute SubstituteInstanceAttribute that tells my setup to sub that type with NSubstitute it's been pretty useful so far.

This be can be cleaned up but has worked well for me so far.

Here is a quick gist: https://gist.github.com/jacob7395/cd0095c825e8ce229d5265df1527eac9

Not cleaned out my types but should be able to make sense of it UserHubConfigurationHelper.BasicProvider just setups all my dependencies with a callback that allows things to be added by the individual tests.

My working plan is to take the above and use a similar thing to spin up a test web application configuring my di in the same way.

@dukesteen
Copy link

Here's what I'm doing with a test webapplicationfactory & testcontainers to spin up a database for each test session

https://gist.github.com/dukesteen/e6aa4784e9164f145198a2f44be53196

@jacob7395 might be useful for you

@jacob7395
Copy link

Thanks, I'll take a look in more detail when I start testing it with the web app hopefully next week 🤞

I have used test web application for NUnit will be interesting to see how the implementation differs with TUnit.

@benjaminsampica
Copy link

Does anyone have an example of a single test project with multiple test bases / abstract classes that need containers? Currently, I've got an abstract WebTestBase which is essentially a WebApplicationFactory that is testing the web api layer and then I've got an abstract BackendTestBase which is testing the application / domain layers. Each type of test inherits from the base class and both setup some containers and whatnot to share across their tests. Unfortunately, using the [Before(TestSession)] attribute is running regardless of whether the tests that are running inherit from the respective abstract class. I'd love to know how others are solving this w/o splitting into another assembly.

@thomhurst
Copy link
Owner Author

thomhurst commented Dec 1, 2024

Does anyone have an example of a single test project with multiple test bases / abstract classes that need containers? Currently, I've got an abstract WebTestBase which is essentially a WebApplicationFactory that is testing the web api layer and then I've got an abstract BackendTestBase which is testing the application / domain layers. Each type of test inherits from the base class and both setup some containers and whatnot to share across their tests. Unfortunately, using the [Before(TestSession)] attribute is running regardless of whether the tests that are running inherit from the respective abstract class. I'd love to know how others are solving this w/o splitting into another assembly.

You could have each container wrapped in its own class, and have that implement IAsyncInitializer - And in the Initialize method, have it start your container.

Also implement IAsyncDisposable to stop/dispose the container.

Inject it into your base class via Constructor injection, or property injection, with the attribute [ClassDataSource<T>(Shared = SharedType.PerTestAssembly)]

The initialize should run on the first test that uses that class, and the dispose on the last test that uses. If no test uses it, they shouldn't be called.

Example of a web application factory is here:

public class MyFactory
: WebApplicationFactory<Program>,
IAsyncInitializer,
IAsyncDisposable
{
public PostgreSqlContainer PostgresSqlContainer { get; } = new PostgreSqlBuilder().Build();
public async Task InitializeAsync()
{
await PostgresSqlContainer.StartAsync();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services => { });
// stick a breakpoint here - will be hit 1st
base.ConfigureWebHost(builder);
}
public new async ValueTask DisposeAsync()
{
await PostgresSqlContainer.DisposeAsync();
await base.DisposeAsync();
}

And how it is injected into a test base class:

[ClassDataSource<MyFactory>(Shared = SharedType.PerTestSession)]
public required MyFactory Factory { get; set; } = null!;

Does that help?

@benjaminsampica
Copy link

benjaminsampica commented Dec 2, 2024

image

@thomhurst Perfect! For anyone wondering here's what a final state might look like (removed a bunch of my specific stuff).

// Project = Tests.csproj
// FilePath = WebTestBase.cs
[ParallelLimiter<WebTestBaseParallelLimit>]
public abstract class WebTestBase
{
    [ClassDataSource<WebTestBaseFactory>(Shared = SharedType.PerTestSession)]
    public required WebTestBaseFactory TestBaseFactory { get; set; } = null!;

    [Before(Test)]
    public async Task BeforeAnyInheritedTests()
    {
        await TestBaseState.ResetDatabaseAsync();
       // other per-test stuff that needs done every test within this base.
    }
}

public class WebTestBaseFactory : WebApplicationFactory<Program>, IAsyncInitializer, IAsyncDisposable
{
    private MsSqlContainer _database = null!;

    public new async ValueTask DisposeAsync()
    {
        await _database.DisposeAsync();
        await base.DisposeAsync();
    }

    public async Task InitializeAsync()
    {
        _database = await MsSqlContainerFactory.CreateAsync();
       
        // This will be hit after the web host is configured so is safe.
        var dbContext = Services.GetRequiredService<ApplicationDbContext>();

        await dbContext.Database.MigrateAsync();
    }

    public async Task ResetDatabaseAsync() =>  // reset the database.

   // Configure Web Host
}

public class WebTestBaseParallelLimit : IParallelLimit
{
    public int Limit => 1;
}
// Project = Tests.csproj
// FilePath = IntegrationTestBase.cs
[ParallelLimiter<IntegrationTestBaseParallelLimit>]
public abstract class IntegrationTestBase
{
    [ClassDataSource<IntegrationTestBaseFactory>(Shared = SharedType.PerTestSession)]
    public required IntegrationTestBaseFactory TestBaseFactory { get; set; } = null!;

    [Before(Test)]
    public async Task BeforeAnyInheritedTests()
    {
        await TestBaseState.ResetDatabaseAsync();
       // other per-test stuff that needs done every test within this base.
    }
}

public class IntegrationTestBaseFactory : IAsyncInitializer, IAsyncDisposable
{
    private MsSqlContainer _database = null!;

    public async ValueTask DisposeAsync()
    {
        await _database.DisposeAsync();
    }

    public async Task InitializeAsync()
    {
        _database = await MsSqlContainerFactory.CreateAsync();
    }

    public async Task ResetDatabaseAsync() =>  // reset the database.
}

public class IntegrationTestBaseParallelLimit : IParallelLimit
{
    public int Limit => 1;
}

@agray
Copy link

agray commented Dec 16, 2024

@schmitch
Copy link

is there a good way to use the lifecyclehook via dotnet test when having something like the following:

  1. shared test project, for setup code like testconatiners etc.
  2. Test Project 1
  3. Test Project 2

what i'm planing would be to create a database container with all migrations in the shared project that will than be used with project 1 and 2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

7 participants