-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from messerli-informatik-ag/retrieve-types-fro…
…m-container-attribute Add attribute for retrieving types from a container as theory data
- Loading branch information
Showing
7 changed files
with
256 additions
and
5 deletions.
There are no files selected for viewing
123 changes: 123 additions & 0 deletions
123
Test.Utility.Test/TypesRegisteredInContainerDataRetrieverTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using Autofac; | ||
using Messerli.CompositionRoot; | ||
using Xunit; | ||
|
||
namespace Messerli.Test.Utility.Test | ||
{ | ||
public class TypesRegisteredInContainerDataRetrieverTest | ||
{ | ||
private interface IFoo | ||
{ | ||
} | ||
|
||
private interface IBar | ||
{ | ||
} | ||
|
||
[Fact] | ||
public async Task RetrievesTypesRegisteredInContainer() | ||
{ | ||
var expectedTypes = new[] { typeof(IFoo), typeof(IBar) }; | ||
var types = await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainer)); | ||
Assert.Equal(expectedTypes, types); | ||
} | ||
|
||
[Fact] | ||
public async Task WorksWithContainerThatNeedsToBeDisposedAsynchronously() | ||
{ | ||
var expectedTypes = new[] { typeof(AsyncDisposable) }; | ||
var types = await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerWithAsyncDisposableObject)); | ||
Assert.Equal(expectedTypes, types); | ||
} | ||
|
||
[Fact] | ||
public async Task ThrowsWhenCreateContainerMethodIsNotStatic() | ||
{ | ||
await Assert.ThrowsAsync<InvalidOperationException>(async () => | ||
{ | ||
await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerNonStatic)); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public async Task ThrowsWhenCreateContainerMethodIsPrivate() | ||
{ | ||
await Assert.ThrowsAsync<InvalidOperationException>(async () => | ||
{ | ||
await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerPrivate)); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public async Task ThrowsWhenCreateContainerHasIncorrectReturnType() | ||
{ | ||
await Assert.ThrowsAsync<InvalidOperationException>(async () => | ||
{ | ||
await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerWithIncorrectReturnType)); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public async Task ThrowsWhenCreateContainerHasParametersType() | ||
{ | ||
await Assert.ThrowsAsync<InvalidOperationException>(async () => | ||
{ | ||
await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerWithParameters)); | ||
}); | ||
} | ||
|
||
public static IContainer CreateContainer() | ||
=> new CompositionRootBuilder() | ||
.RegisterModule<TestModule>() | ||
.Build(); | ||
|
||
public static IContainer CreateContainerWithAsyncDisposableObject() | ||
=> new CompositionRootBuilder() | ||
.RegisterModule(new ModuleBuilder() | ||
.RegisterInstance(new AsyncDisposable()) | ||
.Build()) | ||
.Build(); | ||
|
||
public static string CreateContainerWithIncorrectReturnType() | ||
=> string.Empty; | ||
|
||
public static IContainer CreateContainerWithParameters(string foo) | ||
=> new CompositionRootBuilder().Build(); | ||
|
||
public IContainer CreateContainerNonStatic() | ||
=> new CompositionRootBuilder().Build(); | ||
|
||
private static IContainer CreateContainerPrivate() | ||
=> new CompositionRootBuilder().Build(); | ||
|
||
private static Task<IEnumerable<Type>> GetTypesRegisteredInContainerViaMethod(string createContainerMethodName) | ||
=> TypesRegisteredInContainerRetriever.GetTypesRegisteredInContainerViaMethod( | ||
typeof(TypesRegisteredInContainerDataRetrieverTest), | ||
createContainerMethodName); | ||
|
||
private class TestModule : Module | ||
{ | ||
protected override void Load(ContainerBuilder builder) | ||
{ | ||
builder.RegisterType<Foo>().As<IFoo>(); | ||
builder.RegisterType<Bar>().As<IBar>(); | ||
} | ||
} | ||
|
||
private class Foo : IFoo | ||
{ | ||
} | ||
|
||
private class Bar : IBar | ||
{ | ||
} | ||
|
||
private class AsyncDisposable : IAsyncDisposable | ||
{ | ||
public ValueTask DisposeAsync() => default; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Runtime.ExceptionServices; | ||
using System.Threading.Tasks; | ||
using Autofac; | ||
using Xunit.Sdk; | ||
using static Messerli.Test.Utility.TypesRegisteredInContainerRetriever; | ||
|
||
namespace Messerli.Test.Utility | ||
{ | ||
/// <summary> | ||
/// <para> | ||
/// Provides all types registered in an Autofac <see cref="IContainer"/> as theory data. | ||
/// </para> | ||
/// </summary> | ||
/// <example> | ||
/// <code> | ||
/// using System; | ||
/// using Autofac; | ||
/// using Messerli.CompositionRoot; | ||
/// using Xunit; | ||
/// | ||
/// public sealed class FooModuleTest : IDisposable | ||
/// { | ||
/// private readonly IContainer _container = CreateContainer(); | ||
/// | ||
/// public static IContainer CreateContainer() => new CompositionRootBuilder().Build(); | ||
/// | ||
/// [Theory] | ||
/// [TypesRegisteredInContainerData(nameof(CreateContainer))] | ||
/// public void TypesRegisteredInContainerCanBeResolved(Type type) => _container.Resolve(type); | ||
/// | ||
/// public void Dispose() => _container.Dispose(); | ||
/// } | ||
/// </code> | ||
/// </example> | ||
public sealed class TypesRegisteredInContainerDataAttribute : DataAttribute | ||
{ | ||
private readonly string _createContainerMethodName; | ||
|
||
public TypesRegisteredInContainerDataAttribute(string createContainerMethodName) | ||
{ | ||
_createContainerMethodName = createContainerMethodName; | ||
} | ||
|
||
public override IEnumerable<object[]> GetData(MethodInfo testMethod) | ||
{ | ||
try | ||
{ | ||
return GetTypesRegisteredInContainer(testMethod).Result; | ||
} | ||
catch (AggregateException exception) when (exception.Flatten().InnerExceptions.Count == 1) | ||
{ | ||
ExceptionDispatchInfo.Capture(exception.Flatten().InnerException).Throw(); | ||
throw null!; | ||
} | ||
} | ||
|
||
private async Task<IEnumerable<object[]>> GetTypesRegisteredInContainer(MemberInfo testMethod) | ||
{ | ||
var targetType = testMethod.DeclaringType ?? throw new NullReferenceException(); | ||
var types = await GetTypesRegisteredInContainerViaMethod(targetType, _createContainerMethodName); | ||
return types.Select(type => new[] { type }); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using Autofac; | ||
|
||
namespace Messerli.Test.Utility | ||
{ | ||
internal static class TypesRegisteredInContainerRetriever | ||
{ | ||
public static async Task<IEnumerable<Type>> GetTypesRegisteredInContainerViaMethod(IReflect targetType, string createContainerMethodName) | ||
{ | ||
await using var container = CreateContainer(targetType, createContainerMethodName); | ||
return container.GetRegisteredTypes(); | ||
} | ||
|
||
private static IContainer CreateContainer(IReflect targetType, string createContainerMethodName) | ||
{ | ||
var createContainerMethod = GetCreateContainerMethod(targetType, createContainerMethodName); | ||
return (IContainer?)createContainerMethod.Invoke(null, null) | ||
?? throw new NullReferenceException(); | ||
} | ||
|
||
private static MethodInfo GetCreateContainerMethod(IReflect type, string createContainerMethodName) | ||
{ | ||
var createContainerMethod = type.GetMethod(createContainerMethodName, BindingFlags.Static | BindingFlags.Public) | ||
?? throw new InvalidOperationException("Unable to access container creation method. Make sure it's static and public."); | ||
ValidateCreateContainerParameters(createContainerMethod); | ||
ValidateCreateContainerReturnType(createContainerMethod); | ||
return createContainerMethod; | ||
} | ||
|
||
private static void ValidateCreateContainerParameters(MethodInfo method) | ||
{ | ||
if (method.GetParameters().Any()) | ||
{ | ||
throw new InvalidOperationException("The container creation method must be nullary."); | ||
} | ||
} | ||
|
||
private static void ValidateCreateContainerReturnType(MethodInfo method) | ||
{ | ||
var expectedReturnType = typeof(IContainer); | ||
var returnType = method.ReturnType; | ||
if (!expectedReturnType.IsAssignableFrom(returnType)) | ||
{ | ||
throw new InvalidOperationException( | ||
$"The return type of the container creation method should be '{expectedReturnType.Name}' but was '{returnType}'"); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters