From 91525737cb7c41454c6ab4fba3dbd0a087f79e53 Mon Sep 17 00:00:00 2001 From: Ruben Schmidmeister Date: Mon, 6 Apr 2020 14:01:03 +0200 Subject: [PATCH 1/4] Add attribute for retrieving types from a container as theory data --- ...sRegisteredInContainerDataRetrieverTest.cs | 123 ++++++++++++++++++ Test.Utility/Test.Utility.csproj | 1 + ...TypesRegisteredInContainerDataAttribute.cs | 70 ++++++++++ .../TypesRegisteredInContainerRetriever.cs | 53 ++++++++ 4 files changed, 247 insertions(+) create mode 100644 Test.Utility.Test/TypesRegisteredInContainerDataRetrieverTest.cs create mode 100644 Test.Utility/TypesRegisteredInContainerDataAttribute.cs create mode 100644 Test.Utility/TypesRegisteredInContainerRetriever.cs diff --git a/Test.Utility.Test/TypesRegisteredInContainerDataRetrieverTest.cs b/Test.Utility.Test/TypesRegisteredInContainerDataRetrieverTest.cs new file mode 100644 index 0000000..885d80b --- /dev/null +++ b/Test.Utility.Test/TypesRegisteredInContainerDataRetrieverTest.cs @@ -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(async () => + { + await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerNonStatic)); + }); + } + + [Fact] + public async Task ThrowsWhenCreateContainerMethodIsPrivate() + { + await Assert.ThrowsAsync(async () => + { + await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerPrivate)); + }); + } + + [Fact] + public async Task ThrowsWhenCreateContainerHasIncorrectReturnType() + { + await Assert.ThrowsAsync(async () => + { + await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerWithIncorrectReturnType)); + }); + } + + [Fact] + public async Task ThrowsWhenCreateContainerHasParametersType() + { + await Assert.ThrowsAsync(async () => + { + await GetTypesRegisteredInContainerViaMethod(nameof(CreateContainerWithParameters)); + }); + } + + public static IContainer CreateContainer() + => new CompositionRootBuilder() + .RegisterModule() + .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> GetTypesRegisteredInContainerViaMethod(string createContainerMethodName) + => TypesRegisteredInContainerRetriever.GetTypesRegisteredInContainerViaMethod( + typeof(TypesRegisteredInContainerDataRetrieverTest), + createContainerMethodName); + + private class TestModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As(); + builder.RegisterType().As(); + } + } + + private class Foo : IFoo + { + } + + private class Bar : IBar + { + } + + private class AsyncDisposable : IAsyncDisposable + { + public ValueTask DisposeAsync() => default; + } + } +} diff --git a/Test.Utility/Test.Utility.csproj b/Test.Utility/Test.Utility.csproj index 0e580b7..c571e3f 100755 --- a/Test.Utility/Test.Utility.csproj +++ b/Test.Utility/Test.Utility.csproj @@ -6,6 +6,7 @@ netstandard2.1;netstandard2.0 + 8.0 true diff --git a/Test.Utility/TypesRegisteredInContainerDataAttribute.cs b/Test.Utility/TypesRegisteredInContainerDataAttribute.cs new file mode 100644 index 0000000..cf11541 --- /dev/null +++ b/Test.Utility/TypesRegisteredInContainerDataAttribute.cs @@ -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 +{ + /// + /// + /// Provides all types registered in an Autofac as theory data. + /// + /// + /// + /// + /// 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(); + /// } + /// + /// + public sealed class TypesRegisteredInContainerDataAttribute : DataAttribute + { + private readonly string _createContainerMethodName; + + public TypesRegisteredInContainerDataAttribute(string createContainerMethodName) + { + _createContainerMethodName = createContainerMethodName; + } + + public override IEnumerable 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> GetTypesRegisteredInContainer(MemberInfo testMethod) + { + var targetType = testMethod.DeclaringType ?? throw new NullReferenceException(); + var types = await GetTypesRegisteredInContainerViaMethod(targetType, _createContainerMethodName); + return types.Select(type => new[] { type }); + } + } +} diff --git a/Test.Utility/TypesRegisteredInContainerRetriever.cs b/Test.Utility/TypesRegisteredInContainerRetriever.cs new file mode 100644 index 0000000..591d904 --- /dev/null +++ b/Test.Utility/TypesRegisteredInContainerRetriever.cs @@ -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> 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}'"); + } + } + } +} From 58b7c09abf95f9ab2e900e817b50167ee5db86a2 Mon Sep 17 00:00:00 2001 From: Ruben Schmidmeister Date: Mon, 6 Apr 2020 14:03:22 +0200 Subject: [PATCH 2/4] Update changelog --- changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.md b/changelog.md index b67ba4e..f9562db 100755 --- a/changelog.md +++ b/changelog.md @@ -61,3 +61,7 @@ Rerelease of 0.6.0 with fixed nuget package. ## 0.7.3 - Allow blacklisting of types on `TypesThatNeedToBeImplementedInAssemblyData` attribute using a new attribute: `ExcludedTypes`. + +## Unreleased +- Add `TypesRegisteredInContainerDataAttribute` attribute which provides the types + registered in an Autofac container as xUnit theory data. From bddf6eea715080bb0cc958370728ac6fd053cab0 Mon Sep 17 00:00:00 2001 From: Ruben Schmidmeister Date: Mon, 6 Apr 2020 14:09:48 +0200 Subject: [PATCH 3/4] Make attribute classes more strict --- Test.Utility/ExcludedTypesAttribute.cs | 4 ++-- .../TypesThatNeedToBeImplementedInAssemblyDataAttribute.cs | 2 +- changelog.md | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Test.Utility/ExcludedTypesAttribute.cs b/Test.Utility/ExcludedTypesAttribute.cs index b36fd73..2dd9c2b 100644 --- a/Test.Utility/ExcludedTypesAttribute.cs +++ b/Test.Utility/ExcludedTypesAttribute.cs @@ -7,13 +7,13 @@ namespace Messerli.Test.Utility /// Blacklists a hardcoded list of types. Used together with . /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class ExcludedTypesAttribute : Attribute + public sealed class ExcludedTypesAttribute : Attribute { public ExcludedTypesAttribute(params Type[] types) { Types = types; } - public IEnumerable Types { get; } + internal IEnumerable Types { get; } } } diff --git a/Test.Utility/TypesThatNeedToBeImplementedInAssemblyDataAttribute.cs b/Test.Utility/TypesThatNeedToBeImplementedInAssemblyDataAttribute.cs index d0dbcab..ec26fc9 100644 --- a/Test.Utility/TypesThatNeedToBeImplementedInAssemblyDataAttribute.cs +++ b/Test.Utility/TypesThatNeedToBeImplementedInAssemblyDataAttribute.cs @@ -39,7 +39,7 @@ namespace Messerli.Test.Utility /// } /// /// - public class TypesThatNeedToBeImplementedInAssemblyDataAttribute : DataAttribute + public sealed class TypesThatNeedToBeImplementedInAssemblyDataAttribute : DataAttribute { private readonly string _assemblyName; diff --git a/changelog.md b/changelog.md index f9562db..7e2afa9 100755 --- a/changelog.md +++ b/changelog.md @@ -65,3 +65,5 @@ Rerelease of 0.6.0 with fixed nuget package. ## Unreleased - Add `TypesRegisteredInContainerDataAttribute` attribute which provides the types registered in an Autofac container as xUnit theory data. +- Seal `ExcludedTypesAttribute` and make its `Types` member internal. +- Seal `TypesThatNeedToBeImplementedInAssemblyDataAttribute`. From a016497e6e1f406163f5af86efb93f2d1cc34035 Mon Sep 17 00:00:00 2001 From: Ruben Schmidmeister Date: Mon, 6 Apr 2020 14:13:59 +0200 Subject: [PATCH 4/4] Bump version --- Test.Utility/Test.Utility.csproj | 2 +- changelog.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Test.Utility/Test.Utility.csproj b/Test.Utility/Test.Utility.csproj index c571e3f..4306492 100755 --- a/Test.Utility/Test.Utility.csproj +++ b/Test.Utility/Test.Utility.csproj @@ -11,7 +11,7 @@ Messerli.Test.Utility - 0.7.3 + 0.8.0 Class library to simplify test-setups. https://github.com/messerli-informatik-ag/test-utility git diff --git a/changelog.md b/changelog.md index 7e2afa9..296add4 100755 --- a/changelog.md +++ b/changelog.md @@ -62,7 +62,7 @@ Rerelease of 0.6.0 with fixed nuget package. ## 0.7.3 - Allow blacklisting of types on `TypesThatNeedToBeImplementedInAssemblyData` attribute using a new attribute: `ExcludedTypes`. -## Unreleased +## 0.8.0 - Add `TypesRegisteredInContainerDataAttribute` attribute which provides the types registered in an Autofac container as xUnit theory data. - Seal `ExcludedTypesAttribute` and make its `Types` member internal.