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