Tips for NUnit & XUnit#
These tips apply when using the RunInMem or BDD Verify harnesses.
Tips for NUnit#
TestConfig & IDefaultTestConfig#
If you use a TestConfig, configure it in the AssemblySetup to avoid the one-time reflective cost of looking for an IDefaultTestConfig
Do not use logLine#
RunInMem
contains the logLine
parameter to direct logging output. When not provided, Console.WriteLine is used. NUnit redirects this output to the test results.
Use AppInfo.SetResolver for deterministic AppName#
Use AssemblySetup to set the AppInfo resolver to ensure the AppName is consistent despite which test runner is used.
Tips for XUnit#
XUnit is a bit more cumbersome than NUnit when you want to log results to the test output.
TestConfig & IDefaultTestConfig#
XUnit does not have a concept like AssemblySetup so you'll want to create an IDefaultTestConfig
Set the AppInfoOverride to ensure the AppName is consistent despite which test runner is used.
logLine and ITestOutputHelper#
To log results to the test output, you'll need to use their ITestOutputHelper.
Set logLine
= _testOutputHelper.WriteLine
.
new AppRunner<Git>().RunInMem(args, _testOutputHelper.WriteLine)
AsyncLocal helper#
The CommandDotNet tests use the following extension methods and ambient ITextOutputHelper class to simplify our tests. This gives us a similar experience to NUnit except we still need a constructor for every test class.
public static class Ambient
{
private static readonly AsyncLocal<ITestOutputHelper> TestOutputHelper = new AsyncLocal<ITestOutputHelper>();
public static ITestOutputHelper Output
{
get => TestOutputHelper.Value;
set => TestOutputHelper.Value = value;
}
public static Action<string> WriteLine
{
get
{
var output = Output;
if (output == null)
{
throw new InvalidOperationException($"{nameof(Ambient)}.{nameof(Output)} has not been set for the current test");
}
return output.WriteLine;
}
}
}
public static class AppRunnerScenarioExtensions
{
public static AppRunnerResult RunInMem(
this AppRunner runner,
string args,
Func<TestConsole, string> onReadLine = null,
IEnumerable<string> pipedInput = null)
{
return runner.RunInMem(args, Ambient.WriteLine, onReadLine, pipedInput);
}
public static AppRunnerResult RunInMem(
this AppRunner runner,
string[] args,
Func<TestConsole, string> onReadLine = null,
IEnumerable<string> pipedInput = null)
{
return runner.RunInMem(args, Ambient.WriteLine, onReadLine, pipedInput);
}
public static AppRunnerResult Verify(this AppRunner appRunner, IScenario scenario)
{
// use Test.Default to force testing of TestConfig.GetDefaultFromSubClass()
return appRunner.Verify(Ambient.WriteLine, TestConfig.Default, scenario);
}
}
public class GitTests
{
public GitTests(ITestOutputHelper output)
{
Ambient.Output = output;
}
[Fact]
public void Checkout_NewBranch_WithoutBranchFlag_Fails()
{
new AppRunner<Git>()
.UseDefaultMiddleware()
.Verify(new Scenario
{
When = { Args = "checkout lala" },
Then =
{
ExitCode = 1,
Output = "error: pathspec 'lala' did not match any file(s) known to git"
}
});
}
}
instead of
public class GitTests
{
private readonly ITestOutputHelper _output;
public GitTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void Checkout_NewBranch_WithoutBranchFlag_Fails()
{
new AppRunner<Git>()
.UseDefaultMiddleware()
.Verify(_output.WriteLine, new Scenario
{
When = { Args = "checkout lala" },
Then =
{
ExitCode = 1,
Output = "error: pathspec 'lala' did not match any file(s) known to git"
}
});
}
}