VOOZH about

URL: https://tunit.dev/docs/guides/best-practices/

⇱ Tips & Pitfalls | TUnit


Skip to main content

TUnit-specific tips to avoid common mistakes.

Always Await Assertions

TUnit assertions won't execute without await — the test passes silently. A built-in analyzer warns about this, but it remains the most common TUnit mistake. See Awaiting Assertions for details and examples.

New Instance Per Test

TUnit creates a new instance of your test class for every test method. Instance fields are never shared between tests:

publicclassMyTests
{
privateint _value;// reset to 0 for every test

[Test, NotInParallel]
publicvoidTest1()
{
_value =99;
}

[Test,NotInParallel]
publicasyncTaskTest2()
{
// Fails! _value is 0 — this is a different instance
await Assert.That(_value).IsEqualTo(99);
}
}

If you genuinely need shared state, use static fields — but prefer making tests independent or using [ClassDataSource<>] instead.

Use [DependsOn] for Test Ordering

When tests must run in a specific order, use [DependsOn]. Unlike [NotInParallel(Order = N)], it preserves parallelism for unrelated tests:

[Test]
publicasyncTaskStep1_CreateUser()
{
// Runs first
}

[Test]
[DependsOn(nameof(Step1_CreateUser))]
publicasyncTaskStep2_UpdateUser()
{
// Runs after Step1 completes
// Other unrelated tests still run in parallel
}

[Test]
[DependsOn(nameof(Step2_UpdateUser))]
publicasyncTaskStep3_DeleteUser()
{
// Runs after Step2 completes
}

[DependsOn] explicitly declares dependencies and supports depending on multiple tests. If you find yourself ordering many tests, consider whether they should be a single test or use proper setup/teardown instead.

See Parallelism for [NotInParallel], parallel groups, and concurrency configuration.

Sharing Expensive Resources

For expensive setup shared across tests (web servers, databases, containers), use [ClassDataSource<>] with IAsyncInitializer and IAsyncDisposable:

publicclassTestWebServer:IAsyncInitializer,IAsyncDisposable
{
publicWebApplicationFactory<Program>? Factory {get;privateset;}

publicasyncTaskInitializeAsync()
{
Factory =newWebApplicationFactory<Program>();
await Task.CompletedTask;
}

publicasyncValueTaskDisposeAsync()
{
if(Factory !=null)
await Factory.DisposeAsync();
}
}

[ClassDataSource<TestWebServer>(Shared = SharedType.PerTestSession)]
publicclassApiTests(TestWebServer server)
{
[Test]
publicasyncTaskCan_call_endpoint()
{
var client = server.Factory!.CreateClient();
var response =await client.GetAsync("/api/health");
await Assert.That(response.IsSuccessStatusCode).IsTrue();
}

[Test]
publicasyncTaskCan_get_users()
{
var client = server.Factory!.CreateClient();
var response =await client.GetAsync("/api/users");
await Assert.That(response.IsSuccessStatusCode).IsTrue();
}
}

This approach gives you type-safe constructor injection, automatic lifecycle management, and cross-class sharing via SharedType.

See ClassDataSource for all sharing options.

Choosing the Right Hook Level

  • [Before(Test)] / [After(Test)] — runs before/after each test (most common)
  • [Before(Class)] / [After(Class)] — runs once per test class
  • [Before(Assembly)] / [After(Assembly)] — runs once per test assembly

For shared resources, prefer [ClassDataSource<>] over class/assembly hooks — it handles lifecycle automatically and works across multiple test classes.