diff --git a/IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs b/IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs index 45196d4..78277eb 100644 --- a/IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs +++ b/IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; using Xunit; +using Xunit.Sdk; namespace IntelliTect.TestTools.TestFramework.Tests.TestCaseTests { @@ -201,5 +202,43 @@ public async Task OnlyTestBlockThrowsExpectedExceptionWhenOverridingDefaultFinal // Assert Assert.False(tc.Passed, "Test case did not get marked as Failed when we expected it."); } + + [Fact] + public async Task TestCasePassedIsSetTrueBeforeFinallyBlocksRun() + { + // Arrange + TestCase tc = new TestBuilder() + .AddDependencyInstance(true) + .AddTestBlock() + .AddFinallyBlock() + .Build(); + + // Act + await tc.ExecuteAsync(); + + // Assert + Assert.True(tc.Passed, "Test case did not get marked as Passed when we expected it."); + } + + [Fact] + public async Task TestCasePassedIsSetTrueEvenIfFinallyBlockFails() + { + // Arrange + TestCase tc = new TestBuilder() + .AddDependencyInstance(false) + .AddTestBlock() + .AddFinallyBlock() + .Build(); + + // Act + AggregateException exception = await Assert.ThrowsAsync(() => tc.ExecuteAsync()); + + // Assert + Assert.Equal(2, exception.InnerExceptions.Count); + Assert.Equal(typeof(DivideByZeroException), exception.InnerException?.GetType()); + Assert.Equal(typeof(DivideByZeroException), exception.InnerExceptions[0].GetType()); + Assert.Equal(typeof(TrueException), exception.InnerExceptions[1].GetType()); + Assert.False(tc.Passed, "Test case was marked as Passed when we did not expected it."); + } } } diff --git a/IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs b/IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs index a5ed5d9..fe12e9f 100644 --- a/IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs +++ b/IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs @@ -134,4 +134,12 @@ public async Task Execute() await Task.Delay(1); } } + + public class ExampleBlockCheckingTestSuccess : TestBlock + { + public void Execute(TestCase tc) + { + Assert.True(tc.Passed); + } + } } diff --git a/IntelliTect.TestTools.TestFramework/TestCase.cs b/IntelliTect.TestTools.TestFramework/TestCase.cs index 4efdffc..04a6731 100644 --- a/IntelliTect.TestTools.TestFramework/TestCase.cs +++ b/IntelliTect.TestTools.TestFramework/TestCase.cs @@ -37,22 +37,35 @@ public TestCase(string testCaseName, string testMethodName, int testCaseId, ISer /// If a finally block fails and this property is true, the test case is still considered passed internally, but most unit test frameworks will mark the test failed. /// public bool ThrowOnFinallyBlockException { get; set; } = true; + /// + /// If any test block throws an exception, it will be stored here. Finally block exceptions are stored separately in FinallyBlockExceptions. If this is not null at the end of the test case execution, the test case is considered failed. + /// + public Exception? TestBlockException { get; private set; } + /// + /// Gets the list of exceptions that were thrown during the execution of finally blocks. + /// + /// This collection contains all exceptions that occurred in finally blocks, allowing + /// callers to inspect or handle them after execution. The list is empty if no exceptions were thrown in any + /// finally block. + public List FinallyBlockExceptions { get; } = []; + /// + /// Did the test case pass? This is determined by whether any test block threw an exception. + /// + /// + /// Finally block exceptions do not cause the test case to be marked as failed, but they are still captured and can cause the test case to throw after execution if ThrowOnFinallyBlockException is true. + /// + public bool Passed { get; set; } // May make sense to make some of the below public if it's needed for debugging. // If so, definitely need to change them to internal or private sets. - internal List TestBlocks { get; set; } = new(); - internal List FinallyBlocks { get; set; } = new(); + internal List TestBlocks { get; set; } = []; + internal List FinallyBlocks { get; set; } = []; internal bool HasLogger { get; set; } = true; private ITestCaseLogger? Log { get; set; } private IServiceCollection ServiceCollection { get; } - private Dictionary BlockOutput { get; } = new(); - private Exception? TestBlockException { get; set; } - private List FinallyBlockExceptions { get; } = new(); - - // Has this test case passed? Will only be true if every regular test block succeeds. - public bool Passed { get; set; } + private Dictionary BlockOutput { get; } = []; /// /// Legacy method signature. Executes the test case. NOTE: Prefer to use ExecuteAsync, even if you have no awaitable test blocks. @@ -101,6 +114,16 @@ public async Task ExecuteAsync() } } + if (TestBlockException is null) + { + Passed = true; + Log?.Info("Test case finished successfully."); + } + else + { + Log?.Critical($"Test case failed and will continue to executing Finally blocks. Exception: {TestBlockException}"); + } + foreach (var fb in FinallyBlocks) { if (Log is not null) Log.CurrentTestBlock = fb.Type.ToString(); @@ -116,20 +139,6 @@ public async Task ExecuteAsync() Log?.Critical($"Finally block failed: {FinallyBlockExceptions.LastOrDefault()}"); } } - - if (TestBlockException is null) - { - // Note: This likely needs to be moved up above the finally blocks. - // If a test case "passes" i.e. finishes all of its test blocks, we probably need to know that - // in the finally blocks. - // Need to think about how to handle "passed" state if a finally block fails. - Passed = true; - Log?.Info("Test case finished successfully."); - } - else - { - Log?.Critical($"Test case failed: {TestBlockException}"); - } } services.Dispose();