diff --git a/.gitignore b/.gitignore index 46f0db5..584d96c 100644 --- a/.gitignore +++ b/.gitignore @@ -214,3 +214,6 @@ ModelManifest.xml # Jetbrains .idea + +# GitHub Actions environment files +:GITHUB_ENV diff --git a/IntelliTect.TestTools.Console.Tests/ConsoleAssertTests.cs b/IntelliTect.TestTools.Console.Tests/ConsoleAssertTests.cs index e6c76b5..a3558cf 100644 --- a/IntelliTect.TestTools.Console.Tests/ConsoleAssertTests.cs +++ b/IntelliTect.TestTools.Console.Tests/ConsoleAssertTests.cs @@ -232,4 +232,110 @@ public void ExecuteAsync_GivenVariableCRLFWithNLComparedToCRNL_Success() System.Console.WriteLine(output); }); } + + [TestMethod] + public void ExpectThrows_WhenExceptionIsThrown_CapturesException() + { + const string view = @"Enter a number: <>Error: Invalid input"; + + FormatException exception = ConsoleAssert.ExpectThrows(view, () => + { + System.Console.Write("Enter a number: "); + string input = System.Console.ReadLine(); + System.Console.Write("Error: Invalid input"); + int.Parse(input); // This will throw FormatException + }); + + Assert.IsNotNull(exception); + Assert.IsInstanceOfType(exception); + } + + [TestMethod] + public async Task ExpectThrowsAsync_WhenExceptionIsThrown_CapturesException() + { + const string view = @"Enter a number: <>Error: Invalid input"; + + FormatException exception = await ConsoleAssert.ExpectThrowsAsync(view, async () => + { + await Task.Yield(); + System.Console.Write("Enter a number: "); + string input = System.Console.ReadLine(); + System.Console.Write("Error: Invalid input"); + int.Parse(input); // This will throw FormatException + }); + + Assert.IsNotNull(exception); + Assert.IsInstanceOfType(exception); + } + + [TestMethod] + public void ExpectThrows_WhenNoExceptionIsThrown_ThrowsException() + { + const string view = @"Hello World"; + + Exception exception = Assert.ThrowsExactly(() => + { + ConsoleAssert.ExpectThrows(view, () => + { + System.Console.Write("Hello World"); + // No exception thrown + }); + }); + + StringAssert.Contains(exception.Message, "Expected exception of type FormatException was not thrown"); + } + + [TestMethod] + public async Task ExpectThrowsAsync_WhenNoExceptionIsThrown_ThrowsException() + { + const string view = @"Hello World"; + + Exception exception = await Assert.ThrowsExactlyAsync(async () => + { + await ConsoleAssert.ExpectThrowsAsync(view, async () => + { + await Task.Yield(); + System.Console.Write("Hello World"); + // No exception thrown + }); + }); + + StringAssert.Contains(exception.Message, "Expected exception of type FormatException was not thrown"); + } + + [TestMethod] + public void ExpectThrows_WithDifferentExceptionType_ThrowsOriginalException() + { + const string view = @"Enter a number: <>Error: Invalid input"; + + // Expecting ArgumentException but FormatException is thrown + Assert.ThrowsExactly(() => + { + ConsoleAssert.ExpectThrows(view, () => + { + System.Console.Write("Enter a number: "); + string input = System.Console.ReadLine(); + System.Console.Write("Error: Invalid input"); + int.Parse(input); // This throws FormatException, not ArgumentException + }); + }); + } + + [TestMethod] + public void ExpectThrows_WithNormalizeOptions_AppliesNormalization() + { + const string view = "Hello World\n"; + + ArgumentException exception = ConsoleAssert.ExpectThrows(view, () => + { + System.Console.WriteLine("Hello World"); + throw new ArgumentException("Test exception"); + }, NormalizeOptions.NormalizeLineEndings); + + Assert.IsNotNull(exception); + StringAssert.Contains(exception.Message, "Test exception"); + } } \ No newline at end of file diff --git a/IntelliTect.TestTools.Console/ConsoleAssert.cs b/IntelliTect.TestTools.Console/ConsoleAssert.cs index 7d23f31..a91491c 100644 --- a/IntelliTect.TestTools.Console/ConsoleAssert.cs +++ b/IntelliTect.TestTools.Console/ConsoleAssert.cs @@ -721,4 +721,62 @@ public static Process ExecuteProcess(string expected, string fileName, string ar AssertExpectation(expected, standardOutput, (left, right) => LikeOperator(left, right), "The values are not like (using wildcards) each other"); return process; } + + /// + /// Performs a unit test on a console-based method that is expected to throw an exception. + /// A "view" of what a user would see in their console is provided as a string, + /// where their input (including line-breaks) is surrounded by double + /// less-than/greater-than signs, like so: "Input please: <<Input>>" + /// + /// The type of exception expected to be thrown + /// Expected "view" to be seen on the console, + /// including both input and output + /// Method to be run that is expected to throw an exception + /// Options to normalize input and expected output + /// The exception that was thrown + public static TException ExpectThrows(string expected, + Action action, + NormalizeOptions normalizeOptions = NormalizeOptions.Default) + where TException : Exception + { + try + { + Expect(expected, action, normalizeOptions); + } + catch (TException ex) + { + return ex; + } + + throw new Exception($"Expected exception of type {typeof(TException).Name} was not thrown."); + } + + /// + /// Performs a unit test on a console-based async method that is expected to throw an exception. + /// A "view" of what a user would see in their console is provided as a string, + /// where their input (including line-breaks) is surrounded by double + /// less-than/greater-than signs, like so: "Input please: <<Input>>" + /// + /// The type of exception expected to be thrown + /// Expected "view" to be seen on the console, + /// including both input and output + /// Async method to be run that is expected to throw an exception + /// Options to normalize input and expected output + /// The exception that was thrown + public static async Task ExpectThrowsAsync(string expected, + Func action, + NormalizeOptions normalizeOptions = NormalizeOptions.Default) + where TException : Exception + { + try + { + await ExpectAsync(expected, action, normalizeOptions); + } + catch (TException ex) + { + return ex; + } + + throw new Exception($"Expected exception of type {typeof(TException).Name} was not thrown."); + } } \ No newline at end of file