Further fixup & refactoring of Autofixture attribs

This now also includes reworking the attribute which activates
Autofixture-style resolution upon an IServiceProvider.
This has now been renamed to WithAutofixtureResolutionAttribute.

The new/reworked attribute is composable with other Autofixture
attributes.
This commit is contained in:
Craig Fowler 2021-04-02 13:28:27 +01:00
parent f2b27480fc
commit 282c90f80f
17 changed files with 157 additions and 98 deletions

View file

@ -1,43 +0,0 @@
using System;
using System.Reflection;
using AutoFixture;
using AutoFixture.Kernel;
using AutoFixture.Xunit2;
using Elsa.Testing.Shared.AutoFixture.SpecimenBuilders;
namespace Elsa.Testing.Shared.AutoFixture.Attributes
{
/// <summary>
/// Customizes the parameter so that it creates an <see cref="IServiceProvider"/>
/// using Moq. That mock service provider gets services by resolving them from Autofixture.
/// </summary>
/// <seealso cref="AutofixtureServiceProviderSpecimenBuilder"/>
public class AutofixtureServiceProviderAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter)
=> new AutofixtureServiceProviderCustomization(parameter);
class AutofixtureServiceProviderCustomization : ICustomization
{
readonly ParameterInfo parameter;
public void Customize(IFixture fixture)
{
fixture.Customizations.Insert(0, GetSpecimenBuilder());
}
ISpecimenBuilder GetSpecimenBuilder()
{
var paramSpec = new ParameterSpecification(parameter.ParameterType, parameter.Name);
var specimenBuilder = new AutofixtureServiceProviderSpecimenBuilder();
return new FilteringSpecimenBuilder(specimenBuilder, paramSpec);
}
public AutofixtureServiceProviderCustomization(ParameterInfo parameter)
{
this.parameter = parameter;
}
}
}
}

View file

@ -36,7 +36,7 @@ namespace Elsa.Testing.Shared.AutoFixture.Attributes
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
return new Postprocessor(builder, new ElsaHostBuilderBuilderCommand(builderCustomizer), new IsAnElsaHostBuilderBuilderSpec());
return new Postprocessor(builder, new ElsaHostBuilderBuilderCommand(builderCustomizer), IsInstanceOf.Type<ElsaHostBuilderBuilder>());
}
public ElsaHostBuilderBuilderTransformation(Action<ElsaHostBuilderBuilder> builderCustomizer)

View file

@ -0,0 +1,32 @@
using System;
using System.Reflection;
using AutoFixture;
using AutoFixture.Xunit2;
using Elsa.Testing.Shared.AutoFixture.Behaviors;
using Elsa.Testing.Shared.AutoFixture.SpecimenBuilders;
using Moq;
namespace Elsa.Testing.Shared.AutoFixture.Attributes
{
/// <summary>
/// Customizes the parameter so that the <see cref="IServiceProvider"/>
/// created by Moq will resolve services of the appropriate type, creating
/// them using Autofixture.
/// </summary>
/// <seealso cref="AutofixtureServiceProviderSpecimenBuilder"/>
public class WithAutofixtureResolutionAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter)
=> new AutofixtureServiceProviderCustomization();
class AutofixtureServiceProviderCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
var behavior = BehaviorFactory.GetPostprocessingBehavior<IServiceProvider>(AutofixtureResolutionServiceProviderSpecimenBuilder.AddAutofixtureResolution);
fixture.Behaviors.Add(behavior);
}
}
}
}

View file

@ -0,0 +1,49 @@
using System;
using AutoFixture.Kernel;
using Elsa.Testing.Shared.AutoFixture.Specifications;
namespace Elsa.Testing.Shared.AutoFixture.Behaviors
{
public class BehaviorFactory
{
public static ISpecimenBuilderTransformation GetPostprocessingBehavior<T>(Action<T,ISpecimenContext> postProcessingAction) where T : class
=> new PostprocessingTransformation<T>(postProcessingAction);
public static ISpecimenBuilderTransformation GetPostprocessingBehavior<T>(Action<T> postProcessingAction) where T : class
=> new PostprocessingTransformation<T>((speciman, ctx) => postProcessingAction(speciman));
class PostprocessingTransformation<T> : ISpecimenBuilderTransformation where T : class
{
readonly Action<T,ISpecimenContext> builderCustomizer;
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
var command = new PostprocessingCommand<T>(builderCustomizer);
var spec = IsInstanceOf.Type<T>();
return new Postprocessor(builder, command, spec);
}
public PostprocessingTransformation(Action<T,ISpecimenContext> builderCustomizer)
{
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
}
}
class PostprocessingCommand<T> : ISpecimenCommand where T : class
{
readonly Action<T,ISpecimenContext> builderCustomizer;
public void Execute(object specimen, ISpecimenContext context)
{
var builder = (T) specimen;
builderCustomizer(builder, context);
}
public PostprocessingCommand(Action<T,ISpecimenContext> builderCustomizer)
{
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
}
}
}
}

View file

@ -1,10 +0,0 @@
using AutoFixture.Kernel;
using Elsa.Testing.Shared.Helpers;
namespace Elsa.Testing.Shared.AutoFixture.Specifications
{
public class IsAnElsaHostBuilderBuilderSpec : IRequestSpecification
{
public bool IsSatisfiedBy(object request) => request is ElsaHostBuilderBuilder;
}
}

View file

@ -0,0 +1,20 @@
using System;
using AutoFixture.Kernel;
using Elsa.Testing.Shared.AutoFixture.SpecimenBuilders;
namespace Elsa.Testing.Shared.AutoFixture.Specifications
{
public class IsInstanceOf : IRequestSpecification
{
readonly Type type;
public bool IsSatisfiedBy(object request) => request.IsAnAutofixtureRequestForType(type);
public IsInstanceOf(Type type)
{
this.type = type ?? throw new ArgumentNullException(nameof(type));
}
public static IsInstanceOf Type<T>() => new IsInstanceOf(typeof(T));
}
}

View file

@ -0,0 +1,30 @@
using System;
using AutoFixture.Kernel;
using Moq;
namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
{
public class AutofixtureResolutionServiceProviderSpecimenBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if(!request.IsAnAutofixtureRequestForType<IServiceProvider>())
return new NoSpecimen();
var provider = Mock.Of<IServiceProvider>();
AddAutofixtureResolution(provider, context);
return provider;
}
public static void AddAutofixtureResolution(IServiceProvider provider, ISpecimenContext context)
{
var mock = Mock.Get(provider);
mock.Name = $"Autofixture_{nameof(IServiceProvider)}-{Guid.NewGuid()}";
mock
.Setup(x => x.GetService(It.IsAny<Type>()))
.Returns((Type t) => context.Resolve(t));
}
}
}

View file

@ -1,33 +0,0 @@
using System;
using AutoFixture.Kernel;
using Moq;
namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
{
/// <summary>
/// Creates a mock <see cref="IServiceProvider"/> which gets services
/// by resolving them from Autofixture.
/// </summary>
public class AutofixtureServiceProviderSpecimenBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if(request.IsAnAutofixtureRequestForType<IServiceProvider>())
return GetServiceProvider(context);
return new NoSpecimen();
}
static object GetServiceProvider(ISpecimenContext context)
{
var provider = new Mock<IServiceProvider>();
provider.Name = $"Autofixture_{nameof(IServiceProvider)}-{Guid.NewGuid()}";
provider
.Setup(x => x.GetService(It.IsAny<Type>()))
.Returns((Type t) => context.Resolve(t));
return provider.Object;
}
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Reflection;
namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
@ -15,12 +16,21 @@ namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
/// <param name="request">The request object</param>
/// <typeparam name="T">The desired specimen type</typeparam>
/// <returns><c>true</c> if the <paramref name="request"/> is a request for an instance of <typeparamref name="T"/>; <c>false</c> otherwise.</returns>
public static bool IsAnAutofixtureRequestForType<T>(this object request)
public static bool IsAnAutofixtureRequestForType<T>(this object request) => request.IsAnAutofixtureRequestForType(typeof(T));
/// <summary>
/// Gets a value which indicates if the specified <see cref="object"/> qualifies as an Autofixture
/// request for an instance of the specified generic type.
/// </summary>
/// <param name="request">The request object</param>
/// <param name="type">The desired specimen type</param>
/// <returns><c>true</c> if the <paramref name="request"/> is a request for an instance of <typeparamref name="T"/>; <c>false</c> otherwise.</returns>
public static bool IsAnAutofixtureRequestForType(this object request, Type type)
{
if(Equals(request, typeof(T)))
if(Equals(request, type))
return true;
if(request is ParameterInfo paramInfo && paramInfo.ParameterType == typeof(T))
if(request is ParameterInfo paramInfo && paramInfo.ParameterType == type)
return true;
return false;

View file

@ -25,7 +25,7 @@ namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
}
static IServiceProvider GetServiceProvider(ISpecimenContext context)
=> (IServiceProvider) new AutofixtureServiceProviderSpecimenBuilder().Create(typeof(IServiceProvider), context);
=> (IServiceProvider) new AutofixtureResolutionServiceProviderSpecimenBuilder().Create(typeof(IServiceProvider), context);
static WorkflowExecutionContext GetWorkflowExecutionContext(ISpecimenContext context, IServiceProvider serviceProvider)
=> new WorkflowExecutionContext(serviceProvider, Mock.Of<IWorkflowBlueprint>(), new WorkflowInstance());

View file

@ -20,7 +20,11 @@ namespace Elsa.Testing.Shared.Helpers
/// </summary>
/// <param name="relativePath">The relative path, within the temporary folder.</param>
/// <returns>A path for content within the temporary folder.</returns>
public string GetContainedPath(string relativePath) => Path.Combine(Folder, relativePath);
public string GetContainedPath(string relativePath)
{
Directory.CreateDirectory(Folder);
return Path.Combine(Folder, relativePath);
}
public void Dispose()
{

View file

@ -9,7 +9,7 @@ namespace Elsa.Services
public class ActivityExecutionContextForActivityBlueprintFactoryTests
{
[Theory(DisplayName = "The CreateActivityExecutionContext method should create a context using the activity blueprint, the workflow execution context, cancellation token and injected service provider."), AutoMoqData]
public void CreateActivityExecutionContextCreatesContextUsingBlueprintExecutionContextCancellationTokenAndServiceProvider([AutofixtureServiceProvider] IServiceProvider serviceProvider,
public void CreateActivityExecutionContextCreatesContextUsingBlueprintExecutionContextCancellationTokenAndServiceProvider([WithAutofixtureResolution] IServiceProvider serviceProvider,
IActivityBlueprint activityBlueprint,
[OmitOnRecursion] WorkflowExecutionContext workflowExecutionContext,
CancellationToken cancellationToken)

View file

@ -8,7 +8,7 @@ namespace Elsa.Services.Models
public class ActivityExecutionContextTests
{
[Theory(DisplayName = "The PurgeVariables method should clear the Variables instance associated with the WorkflowInstance associated with the Workflow Execution Context"), AutoMoqData]
public void PurgeVariables_clears_workflow_execution_context_workflow_instance_variables([AutofixtureServiceProvider, Frozen] IServiceProvider serviceProvider,
public void PurgeVariables_clears_workflow_execution_context_workflow_instance_variables([WithAutofixtureResolution, Frozen] IServiceProvider serviceProvider,
[OmitOnRecursion,NoAutoProperties] ActivityExecutionContext sut,
string variableName,
object variableValue)

View file

@ -8,7 +8,7 @@ namespace Elsa.Services.Models
public class WorkflowExecutionContextTests
{
[Theory(DisplayName = "The PurgeVariables method should clear the Variables instance associated with the WorkflowInstance"), AutoMoqData]
public void PurgeVariables_clears_workflow_execution_context_workflow_instance_variables([AutofixtureServiceProvider, Frozen] IServiceProvider serviceProvider,
public void PurgeVariables_clears_workflow_execution_context_workflow_instance_variables([WithAutofixtureResolution, Frozen] IServiceProvider serviceProvider,
[OmitOnRecursion,NoAutoProperties] WorkflowExecutionContext sut,
string variableName,
object variableValue)

View file

@ -10,7 +10,7 @@ namespace Elsa.Services
public class WorkflowBuilderTests
{
[Theory(DisplayName = "The Build method should return a workflow blueprint with the WorkflowBurst persistence behaviour if no behaviour was specified"), AutoMoqData]
public void BuildShouldReturnWorkflowBlueprintWithWorkflowBurstPersistenceBehaviourIfNoBehaviourSpecified([AutofixtureServiceProvider, Frozen] IServiceProvider serviceProvider,
public void BuildShouldReturnWorkflowBlueprintWithWorkflowBurstPersistenceBehaviourIfNoBehaviourSpecified([WithAutofixtureResolution, Frozen] IServiceProvider serviceProvider,
WorkflowBuilder sut,
string idPrefix)
{

View file

@ -11,7 +11,7 @@ namespace Elsa.Services
public class WorkflowExecutionContextForWorkflowBlueprintFactoryTests
{
[Theory(DisplayName = "The CreateWorkflowExecutionContextAsync method returns an execution context using the blueprint, an instance and the service provider"), AutoMoqData]
public async Task CreateWorkflowExecutionContextAsyncReturnsContextWithBlueprintInstanceAndServiceProvider([AutofixtureServiceProvider] IServiceProvider serviceProvider,
public async Task CreateWorkflowExecutionContextAsyncReturnsContextWithBlueprintInstanceAndServiceProvider([WithAutofixtureResolution] IServiceProvider serviceProvider,
IWorkflowFactory workflowFactory,
IWorkflowBlueprint workflowBlueprint,
[OmitOnRecursion] WorkflowInstance instance)

View file

@ -38,7 +38,7 @@ namespace Elsa.UnitTests.Triggers
WorkflowTrigger trigger4,
WorkflowTrigger trigger5,
WorkflowTrigger trigger6,
[AutofixtureServiceProvider] IServiceProvider serviceProvider,
[WithAutofixtureResolution] IServiceProvider serviceProvider,
[NoAutoProperties] WorkflowInstance workflowInstance)
{
Mock.Get(activityTypeService)