Unit testing - adambajguz/Typin GitHub Wiki

Typin provides a convenient way to write functional tests for your applications, thanks to the IConsole interface. While a command running in production uses SystemConsole for console interactions (unless other is specified), you can rely on VirtualConsole in your tests to validate these interactions in a simulated environment.

When you initialize an instance of VirtualConsole, you can supply your own streams which will be used as the application's stdin, stdout, and stderr. You don't have to supply all of them, however, and any remaining streams will be substituted with a no-op stub.

To illustrate how to use this, let's look at an example. Assume you want to test a simple command such as this one:

[Command]
public class ConcatCommand : ICommand
{
    [CommandOption("left")]
    public string Left { get; set; } = "Hello";

    [CommandOption("right")]
    public string Right { get; set; } = "world";

    public ValueTask ExecuteAsync(IConsole console)
    {
        console.Output.Write(Left);
        console.Output.Write(' ');
        console.Output.Write(Right);

        return default;
    }
}
[Test]
public async Task ConcatCommand_Test()
{
    // Arrange
    // a) Manual streams creation
    //await using var stdOut = new MemoryStream();
    //var console = new VirtualConsole(output: stdOut);

    // b) Using CreateBuffered
    var (console, stdOut, _) = VirtualConsole.CreateBuffered();

    var app = new CliApplicationBuilder()
        .AddCommand<ConcatCommand>()
        .UseConsole(console)
        .Build();

    var args = new[] {"--left", "foo", "--right", "bar"};
    var envVars = new Dictionary<string, string>();

    // Act
    await app.RunAsync(args, envVars);
    //var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray());

    // Assert
    Assert.That(stdOut.GetString(), Is.EqualTo("foo bar"));
}

As a general recommendation, it's always more preferable to test at the application level. While you can validate your command's execution adequately simply by testing its ExecuteAsync() method, testing end-to-end also helps you catch bugs related to configuration, such as incorrect option names, parameter order, fallback variable names, etc.

Additionally, it's important to remember that commands in CliFx are not constrained to text and can produce binary data. In such cases, you can still use the above setup but call GetBytes() instead of GetString():

// Act
await app.RunAsync(args, envVars);

// Assert
Assert.That(stdOut.GetBytes(), Is.EqualTo(new byte[] {1, 2, 3, 4, 5}));

In some scenarios the binary data may be too large to load in-memory. If that's the case, it's recommended to use VirtualConsole directly with custom streams.