Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions TUnit.Engine/Services/TestExecution/TestCoordinator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ private async ValueTask ExecuteTestInternalAsync(AbstractExecutableTest test, Ca

_contextRestorer.RestoreContext(test);

// Register event receivers early so that skip event receivers work
// even when the test is skipped before full initialization.
_eventReceiverOrchestrator.RegisterReceivers(test.Context, cancellationToken);

// Check if test was already marked as skipped during registration
// (e.g., by a derived SkipAttribute evaluated in OnTestRegistered).
// This must be checked before any instance creation or retry/timeout logic.
Expand Down
40 changes: 21 additions & 19 deletions TUnit.TestProject/LastTestEventReceiverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,38 +109,37 @@ public ValueTask OnLastTestInAssembly(AssemblyHookContext context, TestContext t
}
}

// Test for skipped event receivers
// Test for skipped event receivers using [Skip] attribute.
// After(Test) hooks don't run for statically skipped tests, so we use a
// DependsOn verification test that runs after the skipped test completes.
public class SkippedEventReceiverTests
{
public static readonly List<string> Events = [];
public static string? CapturedSkipReason = null;

[Before(Test)]
public void ClearEvents()
{
Events.Clear();
CapturedSkipReason = null;
}

[Test, Skip("Testing skip event with custom reason")]
[SkipEventReceiverAttribute]
public async Task SkippedTestWithCustomReason()
{
await Task.Delay(10);
}

[After(Test)]
public async Task VerifySkipEventFired(TestContext context)
[Test]
[DependsOn(nameof(SkippedTestWithCustomReason), ProceedOnFailure = true)]
public async Task Verify_SkipEventReceiver_Fired_Exactly_Once()
{
// Give some time for async event receivers to complete
await Task.Delay(100);
// Events were populated by the SkipEventReceiverAttribute on the skipped test.
// No Before(Test) clearing here — we need to observe the cumulative state.
await Assert.That(Events).Contains("TestSkipped");
await Assert.That(Events).Contains("TestEnd");
await Assert.That(CapturedSkipReason).IsEqualTo("Testing skip event with custom reason");

if (context. Metadata.DisplayName.Contains("SkippedTestWithCustomReason"))
{
await Assert.That(Events).Contains("TestSkipped");
await Assert.That(Events).Contains("TestEnd");
await Assert.That(CapturedSkipReason).IsEqualTo("Testing skip event with custom reason");
}
// Guard against double invocation
var skipCount = Events.Count(e => e == "TestSkipped");
await Assert.That(skipCount).IsEqualTo(1);

var endCount = Events.Count(e => e == "TestEnd");
await Assert.That(endCount).IsEqualTo(1);
}
}

Expand Down Expand Up @@ -200,7 +199,10 @@ public async Task VerifyRuntimeSkipEventFired(TestContext context)
await Assert.That(Events).Contains("TestSkipped");
await Assert.That(Events).Contains("TestEnd");

// Verify TestEnd is called exactly once (not twice)
// Verify events are called exactly once (not twice)
var testSkippedCount = Events.Count(e => e == "TestSkipped");
await Assert.That(testSkippedCount).IsEqualTo(1);

var testEndCount = Events.Count(e => e == "TestEnd");
await Assert.That(testEndCount).IsEqualTo(1);
}
Expand Down
Loading