diff --git a/TUnit.Engine.Tests/DataSourceExceptionPropagationTests.cs b/TUnit.Engine.Tests/DataSourceExceptionPropagationTests.cs index ab8005332a..dce13ef648 100644 --- a/TUnit.Engine.Tests/DataSourceExceptionPropagationTests.cs +++ b/TUnit.Engine.Tests/DataSourceExceptionPropagationTests.cs @@ -53,4 +53,27 @@ await RunTestsWithFilter( } ]); } + + [Test] + public async Task NestedClassDataSource_ConstructorThrows_FailsTestWithException() + { + await RunTestsWithFilter( + "/*/*/NestedDataSourcesThrow/*", + [ + result => result.ResultSummary.Outcome.ShouldBe("Failed"), + result => result.ResultSummary.Counters.Total.ShouldBe(1), + result => result.ResultSummary.Counters.Passed.ShouldBe(0), + result => result.ResultSummary.Counters.Failed.ShouldBe(1), + result => + { + var errorMessage = result.Results.First().Output?.ErrorInfo?.Message; + errorMessage.ShouldNotBeNull("Expected an error message"); + // Should contain the original exception message from the DataSource3 constructor + errorMessage.ShouldContain("Oops something went wrong"); + // Should indicate the nested property injection failure path + errorMessage.ShouldContain("Failed to inject properties for type 'DataSource'"); + errorMessage.ShouldContain("Failed to inject properties for type 'DataSource2'"); + } + ]); + } } diff --git a/TUnit.Engine/Services/TestExecution/TestCoordinator.cs b/TUnit.Engine/Services/TestExecution/TestCoordinator.cs index 683a9e8f4c..8d3797392e 100644 --- a/TUnit.Engine/Services/TestExecution/TestCoordinator.cs +++ b/TUnit.Engine/Services/TestExecution/TestCoordinator.cs @@ -65,6 +65,17 @@ private async ValueTask ExecuteTestInternalAsync(AbstractExecutableTest test, Ca _contextRestorer.RestoreContext(test); + // Check if test was already marked as failed during registration (e.g., property injection failure) + // If so, skip execution and report the failure immediately + var existingResult = test.Context.Execution.Result; + if (existingResult?.State == TestState.Failed) + { + var exception = existingResult.Exception ?? new InvalidOperationException("Test failed during registration"); + await _stateManager.MarkFailedAsync(test, exception).ConfigureAwait(false); + await _eventReceiverOrchestrator.InvokeTestEndEventReceiversAsync(test.Context, cancellationToken).ConfigureAwait(false); + return; + } + // Clear Result and timing from any previous execution (important for repeated tests) test.Context.Execution.Result = null; test.Context.TestStart = null; diff --git a/TUnit.TestProject/NestedDataSourcesThrow.cs b/TUnit.TestProject/NestedDataSourcesThrow.cs new file mode 100644 index 0000000000..b309cf396e --- /dev/null +++ b/TUnit.TestProject/NestedDataSourcesThrow.cs @@ -0,0 +1,61 @@ +using TUnit.Core.Interfaces; + +namespace TUnit.TestProject; + +public class NestedDataSourcesThrow +{ + [ClassDataSource] + public DataSource Data { get; set; } = null!; + + [Test] + public Task Test1() + { + // This test body should never execute - the test should fail during property injection + // with "Oops something went wrong" from DataSource3's constructor + Assert.Fail("Test body should not have executed - expected property injection to fail with 'Oops something went wrong'"); + return Task.CompletedTask; + } + + public class DataSource : IAsyncInitializer + { + [ClassDataSource] + public DataSource2 DataSource2 { get; set; } = null!; + + public bool IsInitialized { get; set; } + + public Task InitializeAsync() + { + IsInitialized = true; + return Task.CompletedTask; + } + } + + public class DataSource2 : IAsyncInitializer + { + [ClassDataSource] + public DataSource3 DataSource3 { get; set; } = null!; + + public bool IsInitialized { get; set; } + + public Task InitializeAsync() + { + IsInitialized = true; + return Task.CompletedTask; + } + } + + public class DataSource3 : IAsyncInitializer + { + public DataSource3() + { + throw new Exception("Oops something went wrong"); + } + public bool IsInitialized { get; set; } + + public Task InitializeAsync() + { + IsInitialized = true; + return Task.CompletedTask; + } + } +}