Test Lifecycle Overview
Understanding TUnit's complete test lifecycle helps you write effective tests and place setup/cleanup logic in the right place. TUnit provides multiple mechanisms for hooking into the lifecycle:
- Hook Attributes (
[Before],[After], etc.) - Method-based hooks - Event Receivers (interfaces like
ITestStartEventReceiver) - Object-based event subscriptions - Initialization Interfaces (
IAsyncInitializer,IAsyncDiscoveryInitializer) - Async object setup - Disposal Interfaces (
IDisposable,IAsyncDisposable) - Resource cleanup
This page provides a complete visual overview of when each mechanism executes.
Complete Lifecycle Diagramâ
Phase 1: Test Discoveryâ
Before any tests execute, TUnit discovers all tests and prepares data sources.
Discovery Phase Detailsâ
| Step | What Happens |
|---|---|
[Before(TestDiscovery)] | Hook runs once before discovery begins |
| Scan Assemblies | Find all methods with [Test] attribute |
| Create Data Sources | Instantiate ClassDataSource<T>, resolve MethodDataSource, etc. |
| Property Injection | Resolve and cache property values for data sources |
IAsyncDiscoveryInitializer | Initialize objects that need to be ready during discovery |
[After(TestDiscovery)] | Hook runs once after discovery completes |
OnTestRegistered | Event fires for each test after registration |
IAsyncInitializer does NOT run during discovery. Only IAsyncDiscoveryInitializer runs at discovery time.
Use IAsyncDiscoveryInitializer when your data source needs async initialization to generate test cases (e.g., loading test data from a database).
Phase 2: Test Executionâ
Per-Test Execution Flowâ
Complete Test Execution Orderâ
Here's the exact order of operations for a single test:
| Order | What Happens | Type |
|---|---|---|
| 1 | [Before(TestSession)] | Hook (once per session) |
| 2 | IFirstTestInTestSessionEventReceiver | Event (once per session) |
| 3 | [BeforeEvery(Assembly)] / [Before(Assembly)] | Hooks (once per assembly) |
| 4 | IFirstTestInAssemblyEventReceiver | Event (once per assembly) |
| 5 | [BeforeEvery(Class)] / [Before(Class)] | Hooks (once per class) |
| 6 | IFirstTestInClassEventReceiver | Event (once per class) |
| 7 | Create test class instance | Constructor runs |
| 8 | Set property values on instance | Cached values applied |
| 9 | IAsyncInitializer.InitializeAsync() | All tracked objects initialized |
| 10 | [BeforeEvery(Test)] | Hook |
| 11 | ITestStartEventReceiver (Early) | Event |
| 12 | [Before(Test)] | Hook (instance method) |
| 13 | ITestStartEventReceiver (Late) | Event |
| 14 | Test Body Execution | Your test code runs |
| 15 | ITestEndEventReceiver (Early) | Event |
| 16 | [After(Test)] | Hook (instance method) |
| 17 | ITestEndEventReceiver (Late) | Event |
| 18 | [AfterEvery(Test)] | Hook |
| 19 | IAsyncDisposable / IDisposable | Test instance disposed |
| 20 | Cleanup tracked objects | Ref count decremented, dispose if 0 |
| 21 | ILastTestInClassEventReceiver | Event (after last test in class) |
| 22 | [After(Class)] / [AfterEvery(Class)] | Hooks (after last test in class) |
| 23 | ILastTestInAssemblyEventReceiver | Event (after last test in assembly) |
| 24 | [After(Assembly)] / [AfterEvery(Assembly)] | Hooks (after last test in assembly) |
| 25 | ILastTestInTestSessionEventReceiver | Event (after last test in session) |
| 26 | [After(TestSession)] | Hook (once per session) |
Initialization Interfacesâ
IAsyncInitializer vs IAsyncDiscoveryInitializerâ
| Interface | When It Runs | Use Case |
|---|---|---|
IAsyncDiscoveryInitializer | During test discovery | Loading data for test case generation |
IAsyncInitializer | During test execution (after [Before(Class)]) | Starting containers, DB connections |
Initialization Orderâ
Objects are initialized depth-first (deepest nested objects first):
// If TestClass has PropertyA, and PropertyA has PropertyB...
// Initialization order: PropertyB â PropertyA â TestClass
Disposal Interfacesâ
When Disposal Happensâ
Disposal by Sharing Typeâ
| SharedType | When Disposed |
|---|---|
None (default) | After each test |
PerClass | After last test in the class |
PerAssembly | After last test in the assembly |
PerTestSession | After test session ends |
Keyed | When all tests using that key complete |
Property Injection Lifecycleâ
Key Pointsâ
- Property values are resolved once during test registration
- Shared objects (
PerClass,PerAssembly, etc.) are created once and reused - Each test gets a new instance of the test class
- Cached values are set on each new test instance
IAsyncInitializerruns after[Before(Class)]hooks
Event Receiver Interfacesâ
All Event Receiver Interfacesâ
| Interface | When Fired | Context |
|---|---|---|
ITestRegisteredEventReceiver | After test discovered | TestRegisteredContext |
IFirstTestInTestSessionEventReceiver | Before first test in session | TestSessionContext |
IFirstTestInAssemblyEventReceiver | Before first test in assembly | AssemblyHookContext |
IFirstTestInClassEventReceiver | Before first test in class | ClassHookContext |
ITestStartEventReceiver | When test begins | TestContext |
ITestEndEventReceiver | When test completes | TestContext |
ITestSkippedEventReceiver | When test is skipped | TestContext |
ILastTestInClassEventReceiver | After last test in class | ClassHookContext |
ILastTestInAssemblyEventReceiver | After last test in assembly | AssemblyHookContext |
ILastTestInTestSessionEventReceiver | After last test in session | TestSessionContext |
Early vs Late Stageâ
For ITestStartEventReceiver and ITestEndEventReceiver:
public class MyAttribute : Attribute, ITestStartEventReceiver
{
// Early = runs BEFORE [Before(Test)]
// Late (default) = runs AFTER [Before(Test)]
public EventReceiverStage Stage => EventReceiverStage.Early;
public ValueTask OnTestStart(TestContext context) => ValueTask.CompletedTask;
}
Hook Attributes Referenceâ
All Hook Typesâ
| Level | Before | After | Method Type |
|---|---|---|---|
| Test Discovery | [Before(TestDiscovery)] | [After(TestDiscovery)] | Static |
| Test Session | [Before(TestSession)] | [After(TestSession)] | Static |
| Assembly | [Before(Assembly)] | [After(Assembly)] | Static |
| Class | [Before(Class)] | [After(Class)] | Static |
| Test | [Before(Test)] | [After(Test)] | Instance |
Before vs BeforeEveryâ
| Attribute | Scope |
|---|---|
[Before(Class)] | Once for this class only |
[BeforeEvery(Class)] | Before every class in session |
[Before(Test)] | Before each test in this class |
[BeforeEvery(Test)] | Before every test in session |
Quick Referenceâ
ââ DISCOVERY âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â [Before(TestDiscovery)] â
â â Scan assemblies for [Test] methods â
â â Create data sources, inject properties â
â â IAsyncDiscoveryInitializer.InitializeAsync() â
â [After(TestDiscovery)] â
â â ITestRegisteredEventReceiver.OnTestRegistered (per test) â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â
âŧ
ââ TEST SESSION ââââââââââââââââââââââââââââââââââââââââââââââââââââ
â [Before(TestSession)] â IFirstTestInTestSessionEventReceiver â
â â â
â ââ [Before(Assembly)] â IFirstTestInAssemblyEventReceiver â
â â â â
â â ââ [Before(Class)] â IFirstTestInClassEventReceiver â
â â â â â
â â â â ââ PER TEST ââââââââââââââââââââââââââââââââââââââ â
â â â â â Create instance (constructor) â â
â â â â â Set property values â â
â â â â â IAsyncInitializer.InitializeAsync() â â
â â â â â [BeforeEvery(Test)] â â
â â â â â ITestStartEventReceiver (Early) â â
â â â â â [Before(Test)] â â
â â â â â ITestStartEventReceiver (Late) â â
â â â â â âââââââââââ TEST BODY âââââââââââ â â
â â â â â ITestEndEventReceiver (Early) â â
â â â â â [After(Test)] â â
â â â â â ITestEndEventReceiver (Late) â â
â â â â â [AfterEvery(Test)] â â
â â â â â IAsyncDisposable / IDisposable â â
â â â â â Cleanup tracked objects â â
â â â â âââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â â â â
â â â ââ ILastTestInClassEventReceiver â [After(Class)] â
â â â â
â â ââ ILastTestInAssemblyEventReceiver â [After(Assembly)] â
â â â
â ââ ILastTestInTestSessionEventReceiver â [After(TestSession)] â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Exception Handlingâ
All [After] hooks, ITestEndEventReceiver events, and disposal methods run even if earlier ones fail. Exceptions are collected and thrown together.
| Phase | Behavior |
|---|---|
| Before hooks | Fail fast (exception stops execution) |
| After hooks | Run all, collect exceptions |
| Disposal | Always runs, exceptions collected |
Related Pagesâ
- Test Set Ups - Detailed guide to
[Before]hooks - Test Clean Ups - Detailed guide to
[After]hooks - Event Subscribing - Event receiver interfaces
- Property Injection - Property injection and
IAsyncInitializer - Dependency Injection - DI integration
- Test Context - Accessing test information