(#555) added TypeRegistrarBaseTests

as a very simple test harness to test implementations of
ITypeRegistrar / ITypeResolver
This commit is contained in:
Nils Andresen 2021-11-13 21:33:58 +01:00 committed by Patrik Svensson
parent 2f6b4f53c4
commit 48df9cf68b
5 changed files with 211 additions and 5 deletions

View File

@ -74,6 +74,9 @@ return app.Run(args);
`TypeRegistrar` is a custom class that must be created by the user. This [example using `Microsoft.Extensions.DependencyInjection` as the container](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Injection) provides an example `TypeRegistrar` and `TypeResolver` that can be added to your application with small adjustments for your DI container.
Hint: If you do write your own implementation of `TypeRegistrar` and `TypeResolver` and you have some form of unit tests in place for your project,
there is a utility `TypeRegistrarBaseTests` available that can be used to ensure your implementations adhere to the required implementation. Simply call `TypeRegistrarBaseTests.RunAllTests()` and expect no `TypeRegistrarBaseTests.TestFailedException` to be thrown.
## Interception
`CommandApp` also provides a `SetInterceptor` configuration. An interceptor is run before all commands are executed. This is typically used for configuring logging or other infrastructure concerns.

View File

@ -0,0 +1,191 @@
using System;
using Spectre.Console.Cli;
namespace Spectre.Console.Testing
{
/// <summary>
/// This is a utility class for implementors of
/// <see cref="ITypeRegistrar"/> and corresponding <see cref="ITypeResolver"/>.
/// </summary>
public sealed class TypeRegistrarBaseTests
{
private readonly Func<ITypeRegistrar> _registrarFactory;
/// <summary>
/// Initializes a new instance of the <see cref="TypeRegistrarBaseTests"/> class.
/// </summary>
/// <param name="registrarFactory">The factory to create a new, clean <see cref="ITypeRegistrar"/> to be used for each test.</param>
public TypeRegistrarBaseTests(Func<ITypeRegistrar> registrarFactory)
{
_registrarFactory = registrarFactory;
}
/// <summary>
/// Runs all tests.
/// </summary>
/// <exception cref="TestFailedException">This exception is raised, if a test fails.</exception>
public void RunAllTests()
{
var testCases = new Action<ITypeRegistrar>[]
{
RegistrationsCanBeResolved,
InstanceRegistrationsCanBeResolved,
LazyRegistrationsCanBeResolved,
ResolvingNotRegisteredServiceReturnsNull,
ResolvingNullTypeReturnsNull,
};
foreach (var test in testCases)
{
test(_registrarFactory());
}
}
private static void ResolvingNullTypeReturnsNull(ITypeRegistrar registrar)
{
// Given no registration
var resolver = registrar.Build();
try
{
// When
var actual = resolver.Resolve(null);
// Then
if (actual != null)
{
throw new TestFailedException(
$"Expected the resolver to resolve null, since null was requested as the service type. Actually resolved {actual.GetType().Name}.");
}
}
catch (Exception ex)
{
throw new TestFailedException(
$"Expected the resolver not to throw, but caught {ex.GetType().Name}.", ex);
}
}
private static void ResolvingNotRegisteredServiceReturnsNull(ITypeRegistrar registrar)
{
// Given no registration
var resolver = registrar.Build();
try
{
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (actual != null)
{
throw new TestFailedException(
$"Expected the resolver to resolve null, since no service was registered. Actually resolved {actual.GetType().Name}.");
}
}
catch (Exception ex)
{
throw new TestFailedException(
$"Expected the resolver not to throw, but caught {ex.GetType().Name}.", ex);
}
}
private static void RegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
registrar.Register(typeof(IMockService), typeof(MockService));
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (actual == null)
{
throw new TestFailedException(
$"Expected the resolver to resolve an instance of {nameof(MockService)}. Actually resolved null.");
}
if (actual is not MockService)
{
throw new TestFailedException(
$"Expected the resolver to resolve an instance of {nameof(MockService)}. Actually resolved {actual.GetType().Name}.");
}
}
private static void InstanceRegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
var instance = new MockService();
registrar.RegisterInstance(typeof(IMockService), instance);
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (!ReferenceEquals(actual, instance))
{
throw new TestFailedException(
"Expected the resolver to resolve exactly the registered instance.");
}
}
private static void LazyRegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
var instance = new MockService();
var factoryCalled = false;
registrar.RegisterLazy(typeof(IMockService), () =>
{
factoryCalled = true;
return instance;
});
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (!factoryCalled)
{
throw new TestFailedException(
"Expected the factory to be called, to resolve the lazy registration.");
}
if (!ReferenceEquals(actual, instance))
{
throw new TestFailedException(
"Expected the resolver to return exactly the result of the lazy-registered factory.");
}
}
/// <summary>
/// internal use only.
/// </summary>
private interface IMockService
{
}
private class MockService : IMockService
{
}
/// <summary>
/// Exception, to be raised when a test fails.
/// </summary>
public sealed class TestFailedException : Exception
{
/// <inheritdoc cref="Exception" />
public TestFailedException(string message)
: base(message)
{
}
/// <inheritdoc cref="Exception" />
public TestFailedException(string message, Exception inner)
: base(message, inner)
{
}
}
}
}

View File

@ -17,6 +17,7 @@
<None Remove="Widgets\Figlet\Fonts\Standard.flf" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
<None Include="..\.editorconfig" Link="Cli\.editorconfig" />
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
</ItemGroup>
<ItemGroup>

View File

@ -6,10 +6,6 @@
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Spectre.Console\Cli\Internal\Constants.cs" Link="Imported\Constants.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="Data\starwars.flf" />
</ItemGroup>
@ -24,7 +20,6 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.6.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="Nullable" Version="1.3.0" PrivateAssets="all" />
<PackageReference Include="Shouldly" Version="4.0.3" />
<PackageReference Include="Spectre.Verify.Extensions" Version="0.3.0" />
<PackageReference Include="Verify.Xunit" Version="9.0.0-beta.1" />

View File

@ -0,0 +1,16 @@
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed class DefaultTypeRegistrarTests
{
[Fact]
public void Should_Pass_Base_Registrar_Tests()
{
var harness = new TypeRegistrarBaseTests(() => new DefaultTypeRegistrar());
harness.RunAllTests();
}
}
}