diff --git a/TUnit.Engine/Building/TestBuilder.cs b/TUnit.Engine/Building/TestBuilder.cs index 23207f39b7..2b0f147699 100644 --- a/TUnit.Engine/Building/TestBuilder.cs +++ b/TUnit.Engine/Building/TestBuilder.cs @@ -957,10 +957,13 @@ public async ValueTask InvokePostResolutionEventsAsync(AbstractExecutableTest te TestContext.Current = context; // Populate TestContext._dependencies from resolved test.Dependencies - // This makes dependencies available to ITestRegisteredEventReceiver + // This makes dependencies available to event receivers PopulateDependencies(test, context._dependencies); - // Invoke test registered event receivers + // Invoke discovery event receivers first (discovery phase) + await InvokeDiscoveryEventReceiversAsync(context); + + // Invoke test registered event receivers (registration phase) try { await InvokeTestRegisteredReceiversAsync(context); @@ -971,10 +974,7 @@ public async ValueTask InvokePostResolutionEventsAsync(AbstractExecutableTest te test.SetResult(TestState.Failed, ex); } - // Invoke discovery event receivers - await InvokeDiscoveryEventReceiversAsync(context); - - // Clear the cached display name after discovery events + // Clear the cached display name after registration events // This ensures that ArgumentDisplayFormatterAttribute and similar attributes // have a chance to register their formatters before the display name is finalized context.InvalidateDisplayNameCache(); diff --git a/TUnit.Engine/TestDiscoveryService.cs b/TUnit.Engine/TestDiscoveryService.cs index e80e088812..0a8f4230a3 100644 --- a/TUnit.Engine/TestDiscoveryService.cs +++ b/TUnit.Engine/TestDiscoveryService.cs @@ -108,8 +108,14 @@ public async Task DiscoverTests(string testSessionId, ITest _dependencyResolver.TryResolveDependencies(test); } - // Now that dependencies are resolved, invoke event receivers - // This ensures ITestRegisteredEventReceiver can access dependency information + // Add tests to context and run After(TestDiscovery) hooks before event receivers + // This marks the end of the discovery phase, before registration begins + contextProvider.TestDiscoveryContext.AddTests(allTests.Select(static t => t.Context)); + await _testExecutor.ExecuteAfterTestDiscoveryHooksAsync(cancellationToken).ConfigureAwait(false); + contextProvider.TestDiscoveryContext.RestoreExecutionContext(); + + // Now invoke event receivers (registration phase) + // ITestRegisteredEventReceiver can access dependency information and any state set by After(TestDiscovery) hooks foreach (var test in allTests) { await _testBuilderPipeline.InvokePostResolutionEventsAsync(test).ConfigureAwait(false); @@ -138,11 +144,6 @@ public async Task DiscoverTests(string testSessionId, ITest filteredTests = [.. testsToInclude]; } - contextProvider.TestDiscoveryContext.AddTests(allTests.Select(static t => t.Context)); - - await _testExecutor.ExecuteAfterTestDiscoveryHooksAsync(cancellationToken).ConfigureAwait(false); - contextProvider.TestDiscoveryContext.RestoreExecutionContext(); - await _testFilterService.RegisterTestsAsync(filteredTests).ConfigureAwait(false); var finalContext = ExecutionContext.Capture(); @@ -190,8 +191,15 @@ public async IAsyncEnumerable DiscoverTestsFullyStreamin _dependencyResolver.TryResolveDependencies(test); } - // Now that dependencies are resolved, invoke event receivers - // This ensures ITestRegisteredEventReceiver can access dependency information + // Add tests to context and run After(TestDiscovery) hooks before event receivers + // This marks the end of the discovery phase, before registration begins + var contextProvider = _testExecutor.GetContextProvider(); + contextProvider.TestDiscoveryContext.AddTests(allTests.Select(static t => t.Context)); + await _testExecutor.ExecuteAfterTestDiscoveryHooksAsync(cancellationToken).ConfigureAwait(false); + contextProvider.TestDiscoveryContext.RestoreExecutionContext(); + + // Now invoke event receivers (registration phase) + // ITestRegisteredEventReceiver can access dependency information and any state set by After(TestDiscovery) hooks foreach (var test in allTests) { await _testBuilderPipeline.InvokePostResolutionEventsAsync(test).ConfigureAwait(false);