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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,6 @@ ModelManifest.xml

# Jetbrains
.idea

# GitHub Actions environment files
:GITHUB_ENV
129 changes: 126 additions & 3 deletions IntelliTect.TestTools.Console.Tests/ConsoleAssertTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public void ConsoleTester_HelloWorld_DontNormalizeCRLF()
{
const string view = "Hello World\r\n";

Assert.ThrowsExactly<Exception>(() =>
Assert.ThrowsExactly<ConsoleAssertException>(() =>
{
ConsoleAssert.Expect(view, () =>
{
Expand All @@ -160,7 +160,7 @@ public void ConsoleTester_HelloWorld_DontNormalizeCRLF()
[DataRow("+hello+world+")]
public void ConsoleTester_OutputIncludesPluses_PlusesAreNotStripped(string consoleInput)
{
Exception exception = Assert.ThrowsExactly<Exception>(() =>
ConsoleAssertException exception = Assert.ThrowsExactly<ConsoleAssertException>(() =>
{
ConsoleAssert.Expect(consoleInput, () =>
{
Expand Down Expand Up @@ -232,4 +232,127 @@ public void ExecuteAsync_GivenVariableCRLFWithNLComparedToCRNL_Success()
System.Console.WriteLine(output);
});
}
}

[TestMethod]
public void ExpectThrows_WhenExceptionIsThrown_CapturesException()
{
const string view = @"Enter a number: <<invalid
>>Error: Invalid input";

FormatException exception = ConsoleAssert.ExpectThrows<FormatException>(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<FormatException>(exception);
}

[TestMethod]
public async Task ExpectThrowsAsync_WhenExceptionIsThrown_CapturesException()
{
const string view = @"Enter a number: <<invalid
>>Error: Invalid input";

FormatException exception = await ConsoleAssert.ExpectThrowsAsync<FormatException>(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<FormatException>(exception);
}

[TestMethod]
public void ExpectThrows_WhenNoExceptionIsThrown_ThrowsException()
{
const string view = @"Hello World";

ConsoleAssertException exception = Assert.ThrowsExactly<ConsoleAssertException>(() =>
{
ConsoleAssert.ExpectThrows<FormatException>(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";

ConsoleAssertException exception = await Assert.ThrowsExactlyAsync<ConsoleAssertException>(async () =>
{
await ConsoleAssert.ExpectThrowsAsync<FormatException>(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: <<invalid
>>Error: Invalid input";

// Expecting ArgumentException but FormatException is thrown
Assert.ThrowsExactly<FormatException>(() =>
{
ConsoleAssert.ExpectThrows<ArgumentException>(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<ArgumentException>(view, () =>
{
System.Console.WriteLine("Hello World");
throw new ArgumentException("Test exception");
}, NormalizeOptions.NormalizeLineEndings);

Assert.IsNotNull(exception);
StringAssert.Contains(exception.Message, "Test exception");
}

[TestMethod]
public void ExpectThrows_WithWrongExpectedOutput_ThrowsConsoleAssertException()
{
const string view = "Wrong output <<invalid\n>>Error: Invalid input";

Assert.ThrowsExactly<ConsoleAssertException>(() =>
{
ConsoleAssert.ExpectThrows<FormatException>(view, () =>
{
System.Console.Write("Enter a number: ");
string input = System.Console.ReadLine();
System.Console.Write("Error: Invalid input");
int.Parse(input);
});
});
}
}
74 changes: 71 additions & 3 deletions IntelliTect.TestTools.Console/ConsoleAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public static void Expect<T>(string expected, Func<string[], T> func, T expected

if (!expectedReturn.Equals(@return))
{
throw new Exception($"The value returned from {nameof(func)} ({@return}) was not the {nameof(expectedReturn)}({expectedReturn}) value.");
throw new ConsoleAssertException($"The value returned from {nameof(func)} ({@return}) was not the {nameof(expectedReturn)}({expectedReturn}) value.");
}
}

Expand Down Expand Up @@ -468,7 +468,7 @@ private static void AssertExpectation(string expectedOutput, string output, Func
bool failTest = !areEquivalentOperator(expectedOutput, output);
if (failTest)
{
throw new Exception(GetMessageText(expectedOutput, output, equivalentOperatorErrorMessage));
throw new ConsoleAssertException(GetMessageText(expectedOutput, output, equivalentOperatorErrorMessage));
}
}

Expand Down Expand Up @@ -721,4 +721,72 @@ 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;
}
}

/// <summary>
/// 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: &lt;&lt;Input&gt;&gt;"
/// </summary>
/// <typeparam name="TException">The type of exception expected to be thrown</typeparam>
/// <param name="expected">Expected "view" to be seen on the console,
/// including both input and output</param>
/// <param name="action">Method to be run that is expected to throw an exception</param>
/// <param name="normalizeOptions">Options to normalize input and expected output</param>
/// <returns>The exception that was thrown</returns>
public static TException ExpectThrows<TException>(string expected,
Action action,
NormalizeOptions normalizeOptions = NormalizeOptions.Default)
where TException : Exception
{
(string input, string expectedOutput) = Parse(expected);
TException caughtException = null;

string output = Execute(input, () =>
{
try { action(); }
catch (TException ex) { caughtException = ex; }
});

if (caughtException is null)
throw new ConsoleAssertException($"Expected exception of type {typeof(TException).Name} was not thrown.");

CompareOutput(output, expectedOutput, normalizeOptions, (l, r) => l == r, "Values are not equal");

return caughtException;
}

/// <summary>
/// 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: &lt;&lt;Input&gt;&gt;"
/// </summary>
/// <typeparam name="TException">The type of exception expected to be thrown</typeparam>
/// <param name="expected">Expected "view" to be seen on the console,
/// including both input and output</param>
/// <param name="action">Async method to be run that is expected to throw an exception</param>
/// <param name="normalizeOptions">Options to normalize input and expected output</param>
/// <returns>The exception that was thrown</returns>
public static async Task<TException> ExpectThrowsAsync<TException>(string expected,
Func<Task> action,
NormalizeOptions normalizeOptions = NormalizeOptions.Default)
where TException : Exception
{
(string input, string expectedOutput) = Parse(expected);
TException caughtException = null;

string output = await ExecuteAsync(input, async () =>
{
try { await action(); }
catch (TException ex) { caughtException = ex; }
});

if (caughtException is null)
throw new ConsoleAssertException($"Expected exception of type {typeof(TException).Name} was not thrown.");

CompareOutput(output, expectedOutput, normalizeOptions, (l, r) => l == r, "Values are not equal");

return caughtException;
}
}
14 changes: 14 additions & 0 deletions IntelliTect.TestTools.Console/ConsoleAssertException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace IntelliTect.TestTools.Console;

/// <summary>
/// Exception thrown when a <see cref="ConsoleAssert"/> assertion fails.
/// </summary>
public sealed class ConsoleAssertException : Exception
{
/// <inheritdoc />
public ConsoleAssertException(string message) : base(message) { }

/// <inheritdoc />
public ConsoleAssertException(string message, Exception innerException)
: base(message, innerException) { }
}
Loading