mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-16 20:23:20 +08:00
* TestConsole can now be configured and accessed in CommandAppTester * Add test with mocked user inputs for interactive command * Add documentation for using the CommandAppTester Co-authored-by: Patrik Svensson <patriksvensson@users.noreply.github.com> Co-authored-by: Marek Magath <Marek.Magath@solarwinds.com>
This commit is contained in:
parent
6105ee2a86
commit
57dd8ee410
@ -63,6 +63,90 @@ The following example validates the exit code and terminal output of a `Spectre.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The following example demonstrates how to mock user inputs for an interactive command.
|
||||||
|
This test (InteractiveCommand_WithMockedUserInputs_ProducesExpectedOutput) simulates user interactions by pushing predefined inputs to the console, then verifies that the resulting output is as expected.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public sealed class InteractiveCommandTests
|
||||||
|
{
|
||||||
|
private sealed class InteractiveCommand : Command
|
||||||
|
{
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
|
||||||
|
public InteractiveCommand(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
_console = console;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Execute(CommandContext context)
|
||||||
|
{
|
||||||
|
var fruits = _console.Prompt(
|
||||||
|
new MultiSelectionPrompt<string>()
|
||||||
|
.Title("What are your [green]favorite fruits[/]?")
|
||||||
|
.NotRequired() // Not required to have a favorite fruit
|
||||||
|
.PageSize(10)
|
||||||
|
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
||||||
|
.InstructionsText(
|
||||||
|
"[grey](Press [blue]<space>[/] to toggle a fruit, " +
|
||||||
|
"[green]<enter>[/] to accept)[/]")
|
||||||
|
.AddChoices(new[] {
|
||||||
|
"Apple", "Apricot", "Avocado",
|
||||||
|
"Banana", "Blackcurrant", "Blueberry",
|
||||||
|
"Cherry", "Cloudberry", "Coconut",
|
||||||
|
}));
|
||||||
|
|
||||||
|
var fruit = _console.Prompt(
|
||||||
|
new SelectionPrompt<string>()
|
||||||
|
.Title("What's your [green]favorite fruit[/]?")
|
||||||
|
.PageSize(10)
|
||||||
|
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
||||||
|
.AddChoices(new[] {
|
||||||
|
"Apple", "Apricot", "Avocado",
|
||||||
|
"Banana", "Blackcurrant", "Blueberry",
|
||||||
|
"Cherry", "Cloudberry", "Cocunut",
|
||||||
|
}));
|
||||||
|
|
||||||
|
var name = _console.Ask<string>("What's your name?");
|
||||||
|
|
||||||
|
_console.WriteLine($"[{string.Join(',', fruits)};{fruit};{name}]");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InteractiveCommand_WithMockedUserInputs_ProducesExpectedOutput()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
TestConsole console = new();
|
||||||
|
console.Interactive();
|
||||||
|
|
||||||
|
// Your mocked inputs must always end with "Enter" for each prompt!
|
||||||
|
|
||||||
|
// Multi selection prompt: Choose first option
|
||||||
|
console.Input.PushKey(ConsoleKey.Spacebar);
|
||||||
|
console.Input.PushKey(ConsoleKey.Enter);
|
||||||
|
|
||||||
|
// Selection prompt: Choose second option
|
||||||
|
console.Input.PushKey(ConsoleKey.DownArrow);
|
||||||
|
console.Input.PushKey(ConsoleKey.Enter);
|
||||||
|
|
||||||
|
// Ask text prompt: Enter name
|
||||||
|
console.Input.PushTextWithEnter("Spectre Console");
|
||||||
|
|
||||||
|
var app = new CommandAppTester(null, new CommandAppTesterSettings(), console);
|
||||||
|
app.SetDefaultCommand<InteractiveCommand>();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Output.EndsWith("[Apple;Apricot;Spectre Console]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Testing console behaviour
|
## Testing console behaviour
|
||||||
|
|
||||||
`TestConsole` and `TestConsoleInput` are testable implementations of `IAnsiConsole` and `IAnsiConsoleInput`, allowing you fine-grain control over testing console output and interactivity.
|
`TestConsole` and `TestConsoleInput` are testable implementations of `IAnsiConsole` and `IAnsiConsoleInput`, allowing you fine-grain control over testing console output and interactivity.
|
||||||
|
@ -8,6 +8,11 @@ public sealed class CommandAppTester
|
|||||||
private Action<CommandApp>? _appConfiguration;
|
private Action<CommandApp>? _appConfiguration;
|
||||||
private Action<IConfigurator>? _configuration;
|
private Action<IConfigurator>? _configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the test console used by both the CommandAppTester and CommandApp.
|
||||||
|
/// </summary>
|
||||||
|
public TestConsole Console { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Registrar to use in the CommandApp.
|
/// Gets or sets the Registrar to use in the CommandApp.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -23,10 +28,15 @@ public sealed class CommandAppTester
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="registrar">The registrar.</param>
|
/// <param name="registrar">The registrar.</param>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
public CommandAppTester(ITypeRegistrar? registrar = null, CommandAppTesterSettings? settings = null)
|
/// <param name="console">The test console that overrides the default one.</param>
|
||||||
|
public CommandAppTester(
|
||||||
|
ITypeRegistrar? registrar = null,
|
||||||
|
CommandAppTesterSettings? settings = null,
|
||||||
|
TestConsole? console = null)
|
||||||
{
|
{
|
||||||
Registrar = registrar;
|
Registrar = registrar;
|
||||||
TestSettings = settings ?? new CommandAppTesterSettings();
|
TestSettings = settings ?? new CommandAppTesterSettings();
|
||||||
|
Console = console ?? new TestConsole().Width(int.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,6 +46,7 @@ public sealed class CommandAppTester
|
|||||||
public CommandAppTester(CommandAppTesterSettings settings)
|
public CommandAppTester(CommandAppTesterSettings settings)
|
||||||
{
|
{
|
||||||
TestSettings = settings;
|
TestSettings = settings;
|
||||||
|
Console = new TestConsole().Width(int.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -85,25 +96,23 @@ public sealed class CommandAppTester
|
|||||||
public CommandAppFailure RunAndCatch<T>(params string[] args)
|
public CommandAppFailure RunAndCatch<T>(params string[] args)
|
||||||
where T : Exception
|
where T : Exception
|
||||||
{
|
{
|
||||||
var console = new TestConsole().Width(int.MaxValue);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Run(args, console, c => c.PropagateExceptions());
|
Run(args, Console, c => c.PropagateExceptions());
|
||||||
throw new InvalidOperationException("Expected an exception to be thrown, but there was none.");
|
throw new InvalidOperationException("Expected an exception to be thrown, but there was none.");
|
||||||
}
|
}
|
||||||
catch (T ex)
|
catch (T ex)
|
||||||
{
|
{
|
||||||
if (ex is CommandAppException commandAppException && commandAppException.Pretty != null)
|
if (ex is CommandAppException commandAppException && commandAppException.Pretty != null)
|
||||||
{
|
{
|
||||||
console.Write(commandAppException.Pretty);
|
Console.Write(commandAppException.Pretty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
console.WriteLine(ex.Message);
|
Console.WriteLine(ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CommandAppFailure(ex, console.Output);
|
return new CommandAppFailure(ex, Console.Output);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -120,8 +129,7 @@ public sealed class CommandAppTester
|
|||||||
/// <returns>The result.</returns>
|
/// <returns>The result.</returns>
|
||||||
public CommandAppResult Run(params string[] args)
|
public CommandAppResult Run(params string[] args)
|
||||||
{
|
{
|
||||||
var console = new TestConsole().Width(int.MaxValue);
|
return Run(args, Console);
|
||||||
return Run(args, console);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandAppResult Run(string[] args, TestConsole console, Action<IConfigurator>? config = null)
|
private CommandAppResult Run(string[] args, TestConsole console, Action<IConfigurator>? config = null)
|
||||||
@ -164,8 +172,7 @@ public sealed class CommandAppTester
|
|||||||
/// <returns>The result.</returns>
|
/// <returns>The result.</returns>
|
||||||
public async Task<CommandAppResult> RunAsync(params string[] args)
|
public async Task<CommandAppResult> RunAsync(params string[] args)
|
||||||
{
|
{
|
||||||
var console = new TestConsole().Width(int.MaxValue);
|
return await RunAsync(args, Console);
|
||||||
return await RunAsync(args, console);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CommandAppResult> RunAsync(string[] args, TestConsole console, Action<IConfigurator>? config = null)
|
private async Task<CommandAppResult> RunAsync(string[] args, TestConsole console, Action<IConfigurator>? config = null)
|
||||||
|
@ -44,4 +44,40 @@ public sealed class CommandAppTesterTests
|
|||||||
// Then
|
// Then
|
||||||
result.Output.ShouldBe(expected);
|
result.Output.ShouldBe(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DefaultCtor_WithoutParameters_CreatesDefaultConsole()
|
||||||
|
{
|
||||||
|
// Given, When
|
||||||
|
CommandAppTester app = new();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
app.Console.ShouldNotBeNull();
|
||||||
|
app.Console.Profile.Width.ShouldBe(int.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DefaultCtor_WithCustomConsole_UsesProvidedInstance()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
TestConsole console = new();
|
||||||
|
|
||||||
|
// When
|
||||||
|
CommandAppTester app = new(null, new CommandAppTesterSettings(), console);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
app.Console.ShouldNotBeNull();
|
||||||
|
app.Console.ShouldBeSameAs(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Ctor_WithSettings_CreatesDefaultConsole()
|
||||||
|
{
|
||||||
|
// Given, When
|
||||||
|
CommandAppTester app = new(new CommandAppTesterSettings());
|
||||||
|
|
||||||
|
// Then
|
||||||
|
app.Console.ShouldNotBeNull();
|
||||||
|
app.Console.Profile.Width.ShouldBe(int.MaxValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
namespace Spectre.Console.Cli.Tests.Unit.Testing;
|
||||||
|
|
||||||
|
public sealed class InteractiveCommandTests
|
||||||
|
{
|
||||||
|
private sealed class InteractiveCommand : Command
|
||||||
|
{
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
|
||||||
|
public InteractiveCommand(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
_console = console;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Execute(CommandContext context)
|
||||||
|
{
|
||||||
|
var fruits = _console.Prompt(
|
||||||
|
new MultiSelectionPrompt<string>()
|
||||||
|
.Title("What are your [green]favorite fruits[/]?")
|
||||||
|
.NotRequired() // Not required to have a favorite fruit
|
||||||
|
.PageSize(10)
|
||||||
|
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
||||||
|
.InstructionsText(
|
||||||
|
"[grey](Press [blue]<space>[/] to toggle a fruit, " +
|
||||||
|
"[green]<enter>[/] to accept)[/]")
|
||||||
|
.AddChoices(new[] {
|
||||||
|
"Apple", "Apricot", "Avocado",
|
||||||
|
"Banana", "Blackcurrant", "Blueberry",
|
||||||
|
"Cherry", "Cloudberry", "Coconut",
|
||||||
|
}));
|
||||||
|
|
||||||
|
var fruit = _console.Prompt(
|
||||||
|
new SelectionPrompt<string>()
|
||||||
|
.Title("What's your [green]favorite fruit[/]?")
|
||||||
|
.PageSize(10)
|
||||||
|
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
||||||
|
.AddChoices(new[] {
|
||||||
|
"Apple", "Apricot", "Avocado",
|
||||||
|
"Banana", "Blackcurrant", "Blueberry",
|
||||||
|
"Cherry", "Cloudberry", "Cocunut",
|
||||||
|
}));
|
||||||
|
|
||||||
|
var name = _console.Ask<string>("What's your name?");
|
||||||
|
|
||||||
|
_console.WriteLine($"[{string.Join(',', fruits)};{fruit};{name}]");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InteractiveCommand_WithMockedUserInputs_ProducesExpectedOutput()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
TestConsole console = new();
|
||||||
|
console.Interactive();
|
||||||
|
|
||||||
|
// Your mocked inputs must always end with "Enter" for each prompt!
|
||||||
|
|
||||||
|
// Multi selection prompt: Choose first option
|
||||||
|
console.Input.PushKey(ConsoleKey.Spacebar);
|
||||||
|
console.Input.PushKey(ConsoleKey.Enter);
|
||||||
|
|
||||||
|
// Selection prompt: Choose second option
|
||||||
|
console.Input.PushKey(ConsoleKey.DownArrow);
|
||||||
|
console.Input.PushKey(ConsoleKey.Enter);
|
||||||
|
|
||||||
|
// Ask text prompt: Enter name
|
||||||
|
console.Input.PushTextWithEnter("Spectre Console");
|
||||||
|
|
||||||
|
var app = new CommandAppTester(null, new CommandAppTesterSettings(), console);
|
||||||
|
app.SetDefaultCommand<InteractiveCommand>();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Output.EndsWith("[Apple;Apricot;Spectre Console]");
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user