Skip to content

Commit 4ae6bd9

Browse files
authored
Changes to handling test passing and exceptions (#83)
* Pass the test immediately if all test blocks pass and expose TestBlockException get and FinallyBlockExceptions * Only expose readonly finally exceptions
1 parent ccfcd5f commit 4ae6bd9

File tree

3 files changed

+99
-22
lines changed

3 files changed

+99
-22
lines changed

IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/FinallyExecutionTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Threading.Tasks;
55
using Xunit;
6+
using Xunit.Sdk;
67

78
namespace IntelliTect.TestTools.TestFramework.Tests.TestCaseTests
89
{
@@ -201,5 +202,62 @@ public async Task OnlyTestBlockThrowsExpectedExceptionWhenOverridingDefaultFinal
201202
// Assert
202203
Assert.False(tc.Passed, "Test case did not get marked as Failed when we expected it.");
203204
}
205+
206+
[Fact]
207+
public async Task TestCasePassedIsSetTrueBeforeFinallyBlocksRun()
208+
{
209+
// Arrange
210+
TestCase tc = new TestBuilder()
211+
.AddDependencyInstance(true)
212+
.AddTestBlock<ExampleTestBlockWithBoolReturn>()
213+
.AddFinallyBlock<ExampleBlockCheckingTestSuccess>()
214+
.Build();
215+
216+
// Act
217+
await tc.ExecuteAsync();
218+
219+
// Assert
220+
Assert.True(tc.Passed, "Test case did not get marked as Passed when we expected it.");
221+
}
222+
223+
[Fact]
224+
public async Task TestCasePassedIsSetTrueEvenIfFinallyBlockFails()
225+
{
226+
// Arrange
227+
TestCase tc = new TestBuilder()
228+
.AddDependencyInstance(false)
229+
.AddTestBlock<ExampleTestBlockWithBoolReturn>()
230+
.AddFinallyBlock<ExampleBlockCheckingTestSuccess>()
231+
.Build();
232+
233+
// Act
234+
AggregateException exception = await Assert.ThrowsAsync<AggregateException>(() => tc.ExecuteAsync());
235+
236+
// Assert
237+
Assert.Equal(2, exception.InnerExceptions.Count);
238+
Assert.Equal(typeof(DivideByZeroException), exception.InnerException?.GetType());
239+
Assert.Equal(typeof(DivideByZeroException), exception.InnerExceptions[0].GetType());
240+
Assert.Equal(typeof(TrueException), exception.InnerExceptions[1].GetType());
241+
Assert.False(tc.Passed, "Test case was marked as Passed when we did not expected it.");
242+
}
243+
244+
[Fact]
245+
public async Task CurrentFinallyBlockExceptionCountIncrementsWithEveryFailure()
246+
{
247+
// Arrange
248+
TestCase tc = new TestBuilder()
249+
.AddFinallyBlock<ExampleFinallyBlock>(false)
250+
.AddFinallyBlock<ExampleFinallyBlock>(false)
251+
.Build();
252+
tc.ThrowOnFinallyBlockException = false;
253+
254+
// Act
255+
await tc.ExecuteAsync();
256+
257+
// Assert
258+
var test = tc.CurrentFinallyBlockExceptions;
259+
Assert.Equal(2, tc.CurrentFinallyBlockExceptions.Count);
260+
Assert.True(tc.Passed, "Test case did not get marked as Passed when we expected it.");
261+
}
204262
}
205263
}

IntelliTect.TestTools.TestFramework.Tests/TestData/Dependencies/SimulatorClasses.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,12 @@ public async Task Execute()
134134
await Task.Delay(1);
135135
}
136136
}
137+
138+
public class ExampleBlockCheckingTestSuccess : TestBlock
139+
{
140+
public void Execute(TestCase tc)
141+
{
142+
Assert.True(tc.Passed);
143+
}
144+
}
137145
}

IntelliTect.TestTools.TestFramework/TestCase.cs

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,37 @@ public TestCase(string testCaseName, string testMethodName, int testCaseId, ISer
3737
/// 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.
3838
/// </summary>
3939
public bool ThrowOnFinallyBlockException { get; set; } = true;
40+
/// <summary>
41+
/// 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.
42+
/// </summary>
43+
public Exception? TestBlockException { get; private set; }
44+
/// <summary>
45+
/// Gets the list of exceptions that were thrown during the execution of finally blocks.
46+
/// </summary>
47+
/// <remarks>This collection contains all exceptions that occurred in finally blocks, allowing
48+
/// callers to inspect or handle them after execution. The list is empty if no exceptions were thrown in any
49+
/// finally block.</remarks>
50+
//public readonly List<Exception> FinallBlockExceptions { get; } = [];
51+
public IReadOnlyList<Exception> CurrentFinallyBlockExceptions => FinallyBlockExceptions;
52+
/// <summary>
53+
/// Did the test case pass? This is determined by whether any test block threw an exception.
54+
/// </summary>
55+
/// <remarks>
56+
/// 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.
57+
/// </remarks>
58+
public bool Passed { get; set; }
4059

4160
// May make sense to make some of the below public if it's needed for debugging.
4261
// If so, definitely need to change them to internal or private sets.
4362

44-
internal List<Block> TestBlocks { get; set; } = new();
45-
internal List<Block> FinallyBlocks { get; set; } = new();
63+
internal List<Block> TestBlocks { get; set; } = [];
64+
internal List<Block> FinallyBlocks { get; set; } = [];
4665
internal bool HasLogger { get; set; } = true;
4766

4867
private ITestCaseLogger? Log { get; set; }
4968
private IServiceCollection ServiceCollection { get; }
50-
private Dictionary<Type, object> BlockOutput { get; } = new();
51-
private Exception? TestBlockException { get; set; }
52-
private List<Exception> FinallyBlockExceptions { get; } = new();
53-
54-
// Has this test case passed? Will only be true if every regular test block succeeds.
55-
public bool Passed { get; set; }
69+
private Dictionary<Type, object> BlockOutput { get; } = [];
70+
private List<Exception> FinallyBlockExceptions { get; } = [];
5671

5772
/// <summary>
5873
/// Legacy method signature. Executes the test case. NOTE: Prefer to use ExecuteAsync, even if you have no awaitable test blocks.
@@ -101,6 +116,16 @@ public async Task ExecuteAsync()
101116
}
102117
}
103118

119+
if (TestBlockException is null)
120+
{
121+
Passed = true;
122+
Log?.Info("Test case finished successfully.");
123+
}
124+
else
125+
{
126+
Log?.Critical($"Test case failed and will continue to executing Finally blocks. Exception: {TestBlockException}");
127+
}
128+
104129
foreach (var fb in FinallyBlocks)
105130
{
106131
if (Log is not null) Log.CurrentTestBlock = fb.Type.ToString();
@@ -116,20 +141,6 @@ public async Task ExecuteAsync()
116141
Log?.Critical($"Finally block failed: {FinallyBlockExceptions.LastOrDefault()}");
117142
}
118143
}
119-
120-
if (TestBlockException is null)
121-
{
122-
// Note: This likely needs to be moved up above the finally blocks.
123-
// If a test case "passes" i.e. finishes all of its test blocks, we probably need to know that
124-
// in the finally blocks.
125-
// Need to think about how to handle "passed" state if a finally block fails.
126-
Passed = true;
127-
Log?.Info("Test case finished successfully.");
128-
}
129-
else
130-
{
131-
Log?.Critical($"Test case failed: {TestBlockException}");
132-
}
133144
}
134145

135146
services.Dispose();

0 commit comments

Comments
 (0)