From 0ec70a44db3936bf15e6da432b3ded32250b3a50 Mon Sep 17 00:00:00 2001
From: Frank Ray <52075808+FrankRay78@users.noreply.github.com>
Date: Fri, 19 May 2023 17:19:17 +0100
Subject: [PATCH] Async command unit tests
---
.../Cli/CommandAppTester.cs | 46 ++++++++++++
.../Data/Commands/AsynchronousCommand.cs | 28 ++++++++
.../Data/Commands/ThrowingCommand.cs | 6 +-
.../Settings/AsynchronousCommandSettings.cs | 8 +++
.../Data/Settings/ThrowingCommandSettings.cs | 5 ++
.../Unit/CommandAppTests.Async.cs | 70 +++++++++++++++++++
6 files changed, 158 insertions(+), 5 deletions(-)
create mode 100644 test/Spectre.Console.Cli.Tests/Data/Commands/AsynchronousCommand.cs
create mode 100644 test/Spectre.Console.Cli.Tests/Data/Settings/AsynchronousCommandSettings.cs
create mode 100644 test/Spectre.Console.Cli.Tests/Data/Settings/ThrowingCommandSettings.cs
create mode 100644 test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Async.cs
diff --git a/src/Spectre.Console.Testing/Cli/CommandAppTester.cs b/src/Spectre.Console.Testing/Cli/CommandAppTester.cs
index d06ccd4..f53f60c 100644
--- a/src/Spectre.Console.Testing/Cli/CommandAppTester.cs
+++ b/src/Spectre.Console.Testing/Cli/CommandAppTester.cs
@@ -135,6 +135,52 @@ public sealed class CommandAppTester
var result = app.Run(args);
+ var output = console.Output
+ .NormalizeLineEndings()
+ .TrimLines()
+ .Trim();
+
+ return new CommandAppResult(result, output, context, settings);
+ }
+
+ ///
+ /// Runs the command application asynchronously.
+ ///
+ /// The arguments.
+ /// The result.
+ public async Task RunAsync(params string[] args)
+ {
+ var console = new TestConsole().Width(int.MaxValue);
+ return await RunAsync(args, console);
+ }
+
+ private async Task RunAsync(string[] args, TestConsole console, Action? config = null)
+ {
+ CommandContext? context = null;
+ CommandSettings? settings = null;
+
+ var app = new CommandApp(Registrar);
+ _appConfiguration?.Invoke(app);
+
+ if (_configuration != null)
+ {
+ app.Configure(_configuration);
+ }
+
+ if (config != null)
+ {
+ app.Configure(config);
+ }
+
+ app.Configure(c => c.ConfigureConsole(console));
+ app.Configure(c => c.SetInterceptor(new CallbackCommandInterceptor((ctx, s) =>
+ {
+ context = ctx;
+ settings = s;
+ })));
+
+ var result = await app.RunAsync(args);
+
var output = console.Output
.NormalizeLineEndings()
.TrimLines()
diff --git a/test/Spectre.Console.Cli.Tests/Data/Commands/AsynchronousCommand.cs b/test/Spectre.Console.Cli.Tests/Data/Commands/AsynchronousCommand.cs
new file mode 100644
index 0000000..100d187
--- /dev/null
+++ b/test/Spectre.Console.Cli.Tests/Data/Commands/AsynchronousCommand.cs
@@ -0,0 +1,28 @@
+namespace Spectre.Console.Tests.Data;
+
+public sealed class AsynchronousCommand : AsyncCommand
+{
+ private readonly IAnsiConsole _console;
+
+ public AsynchronousCommand(IAnsiConsole console)
+ {
+ _console = console;
+ }
+
+ public async override Task ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings)
+ {
+ // Simulate a long running asynchronous task
+ await Task.Delay(200);
+
+ if (settings.ThrowException)
+ {
+ throw new Exception($"Throwing exception asynchronously");
+ }
+ else
+ {
+ _console.WriteLine($"Finished executing asynchronously");
+ }
+
+ return 0;
+ }
+}
diff --git a/test/Spectre.Console.Cli.Tests/Data/Commands/ThrowingCommand.cs b/test/Spectre.Console.Cli.Tests/Data/Commands/ThrowingCommand.cs
index 686bf4a..4804525 100644
--- a/test/Spectre.Console.Cli.Tests/Data/Commands/ThrowingCommand.cs
+++ b/test/Spectre.Console.Cli.Tests/Data/Commands/ThrowingCommand.cs
@@ -6,8 +6,4 @@ public sealed class ThrowingCommand : Command
{
throw new InvalidOperationException("W00t?");
}
-}
-
-public sealed class ThrowingCommandSettings : CommandSettings
-{
-}
+}
\ No newline at end of file
diff --git a/test/Spectre.Console.Cli.Tests/Data/Settings/AsynchronousCommandSettings.cs b/test/Spectre.Console.Cli.Tests/Data/Settings/AsynchronousCommandSettings.cs
new file mode 100644
index 0000000..b7bb8e2
--- /dev/null
+++ b/test/Spectre.Console.Cli.Tests/Data/Settings/AsynchronousCommandSettings.cs
@@ -0,0 +1,8 @@
+namespace Spectre.Console.Tests.Data;
+
+public sealed class AsynchronousCommandSettings : CommandSettings
+{
+ [CommandOption("--ThrowException")]
+ [DefaultValue(false)]
+ public bool ThrowException { get; set; }
+}
\ No newline at end of file
diff --git a/test/Spectre.Console.Cli.Tests/Data/Settings/ThrowingCommandSettings.cs b/test/Spectre.Console.Cli.Tests/Data/Settings/ThrowingCommandSettings.cs
new file mode 100644
index 0000000..88a28ff
--- /dev/null
+++ b/test/Spectre.Console.Cli.Tests/Data/Settings/ThrowingCommandSettings.cs
@@ -0,0 +1,5 @@
+namespace Spectre.Console.Tests.Data;
+
+public sealed class ThrowingCommandSettings : CommandSettings
+{
+}
diff --git a/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Async.cs b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Async.cs
new file mode 100644
index 0000000..ee88b0b
--- /dev/null
+++ b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Async.cs
@@ -0,0 +1,70 @@
+namespace Spectre.Console.Tests.Unit.Cli;
+
+public sealed partial class CommandAppTests
+{
+ public sealed class Async
+ {
+ [Fact]
+ public async void Should_Execute_Command_Asynchronously()
+ {
+ // Given
+ var app = new CommandAppTester();
+ app.SetDefaultCommand();
+ app.Configure(config =>
+ {
+ config.PropagateExceptions();
+ });
+
+ // When
+ var result = await app.RunAsync();
+
+ // Then
+ result.ExitCode.ShouldBe(0);
+ result.Output.ShouldBe("Finished executing asynchronously");
+ }
+
+ [Fact]
+ public async void Should_Handle_Exception_Asynchronously()
+ {
+ // Given
+ var app = new CommandAppTester();
+ app.SetDefaultCommand();
+
+ // When
+ var result = await app.RunAsync(new[]
+ {
+ "--ThrowException",
+ "true",
+ });
+
+ // Then
+ result.ExitCode.ShouldBe(-1);
+ }
+
+ [Fact]
+ public async void Should_Throw_Exception_Asynchronously()
+ {
+ // Given
+ var app = new CommandAppTester();
+ app.SetDefaultCommand();
+ app.Configure(config =>
+ {
+ config.PropagateExceptions();
+ });
+
+ // When
+ var result = await Record.ExceptionAsync(async () =>
+ await app.RunAsync(new[]
+ {
+ "--ThrowException",
+ "true",
+ }));
+
+ // Then
+ result.ShouldBeOfType().And(ex =>
+ {
+ ex.Message.ShouldBe("Throwing exception asynchronously");
+ });
+ }
+ }
+}
\ No newline at end of file