WIP #552 - Refactor test logic (reusability)

This refactor of integration test logic is to boost its
reusability.  Moving some functionality to attributes &
customization classes means I can reuse elsewhere without
duplication.
This commit is contained in:
Craig Fowler 2021-03-06 13:55:19 +00:00
parent dc95276abb
commit cbc7c2d461
9 changed files with 164 additions and 40 deletions

View file

@ -0,0 +1,21 @@
using System.Reflection;
using AutoFixture;
using AutoFixture.Xunit2;
using Elsa.Activities.Temporal;
using Elsa.Testing.Shared.AutoFixture.Customizations;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Core.IntegrationTests.Extensions
{
public class HostBuilderWithElsaAndCommonTemporalActivitiesAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new HostBubilderUsingServicesCustomization(services => {
services.AddElsa(elsa => {
CommonTemporalActivityServices.AddCommonTemporalActivities(elsa);
});
}, parameter);
}
}
}

View file

@ -0,0 +1,21 @@
using System.Reflection;
using AutoFixture;
using AutoFixture.Xunit2;
using Elsa.Testing.Shared.AutoFixture.Customizations;
using Hangfire;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Core.IntegrationTests.Extensions
{
public class HostBuilderWithElsaAndHangfireAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new HostBubilderUsingServicesCustomization(services => {
services.AddElsa(elsa => {
elsa.AddHangfireTemporalActivities(config => config.UseInMemoryStorage());
});
}, parameter);
}
}
}

View file

@ -0,0 +1,20 @@
using System.Reflection;
using AutoFixture;
using AutoFixture.Xunit2;
using Elsa.Testing.Shared.AutoFixture.Customizations;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Core.IntegrationTests.Extensions
{
public class HostBuilderWithElsaAndQuartzAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new HostBubilderUsingServicesCustomization(services => {
services.AddElsa(elsa => {
elsa.AddQuartzTemporalActivities();
});
}, parameter);
}
}
}

View file

@ -6,20 +6,15 @@ using Hangfire;
using System.Threading.Tasks;
using System.Threading;
using Elsa.Activities.Temporal;
using Elsa.Core.IntegrationTests.Extensions;
namespace Elsa.UnitTests.Extensions
{
public class TemporalServiceCollectionExtensionsTests
{
[Fact(DisplayName = "Starting a hosted app which uses only AddCommonTemporalActivities should throw InvalidOperationException because of the missing impl")]
public void AddCommonTemporalActivitiesThrowsDuringStartupIfNoTemporalImplementationPresent()
[Theory(DisplayName = "Starting a hosted app which uses only AddCommonTemporalActivities should throw InvalidOperationException because of the missing impl"), AutoMoqData]
public void AddCommonTemporalActivitiesThrowsDuringStartupIfNoTemporalImplementationPresent([HostBuilderWithElsaAndCommonTemporalActivities] IHostBuilder hostBuilder)
{
var hostBuilder = CreateHostBuilderUsingServices(services => {
services.AddElsa(elsa => {
CommonTemporalActivityServices.AddCommonTemporalActivities(elsa);
});
});
var cancellationSource = new CancellationTokenSource();
try
{
@ -33,15 +28,9 @@ namespace Elsa.UnitTests.Extensions
}
}
[Fact(DisplayName = "Starting a hosted app which uses AddHangfireTemporalActivities should not throw")]
public void AddHangfireTemporalActivitiesDoesNotThrowDuringStartup()
[Theory(DisplayName = "Starting a hosted app which uses AddHangfireTemporalActivities should not throw"), AutoMoqData]
public void AddHangfireTemporalActivitiesDoesNotThrowDuringStartup([HostBuilderWithElsaAndHangfire] IHostBuilder hostBuilder)
{
var hostBuilder = CreateHostBuilderUsingServices(services => {
services.AddElsa(elsa => {
elsa.AddHangfireTemporalActivities(config => config.UseInMemoryStorage());
});
});
var cancellationSource = new CancellationTokenSource();
try
{
@ -57,15 +46,9 @@ namespace Elsa.UnitTests.Extensions
}
}
[Fact(DisplayName = "Starting a hosted app which uses AddQuartzTemporalActivities should not throw")]
public void AddQuartzTemporalActivitiesDoesNotThrowDuringStartup()
[Theory(DisplayName = "Starting a hosted app which uses AddQuartzTemporalActivities should not throw"), AutoMoqData]
public void AddQuartzTemporalActivitiesDoesNotThrowDuringStartup([HostBuilderWithElsaAndQuartz] IHostBuilder hostBuilder)
{
var hostBuilder = CreateHostBuilderUsingServices(services => {
services.AddElsa(elsa => {
elsa.AddQuartzTemporalActivities();
});
});
var cancellationSource = new CancellationTokenSource();
try
{
@ -81,15 +64,6 @@ namespace Elsa.UnitTests.Extensions
}
}
/// <summary>
/// Convenience method creates an <see cref="IHostBuilder"/> using the specified services.
/// </summary>
/// <param name="serviceConfig">Services to use in the created host builder</param>
/// <returns>A host builder</returns>
static IHostBuilder CreateHostBuilderUsingServices(Action<IServiceCollection> serviceConfig)
=> Host.CreateDefaultBuilder()
.ConfigureServices((hostBuilder, services) => serviceConfig(services));
/// <summary>
/// Starts up the specified <paramref name="hostBuilder"/> instance using console lifetime.
/// The current thread is then blocked for a short while, giving the app a chance to start up and run.
@ -111,10 +85,10 @@ namespace Elsa.UnitTests.Extensions
/// <param name="cancellationSource">The cancellation source</param>
static void CancelAndSquelchExceptions(CancellationTokenSource cancellationSource)
{
try { cancellationSource.Cancel(); }
catch(Exception) {
// This is only ever used as teardown code, which is why we don't care about exceptions.
}
try { cancellationSource.Cancel(); }
catch(Exception) {
// This is only ever used as teardown code, which is why we don't care about exceptions.
}
}
}
}

View file

@ -0,0 +1,37 @@
using System;
using System.Reflection;
using AutoFixture;
using AutoFixture.Kernel;
using Elsa.Testing.Shared.AutoFixture.SpecimenBuilders;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Testing.Shared.AutoFixture.Customizations
{
public class HostBubilderUsingServicesCustomization : ICustomization
{
readonly Action<IServiceCollection> serviceConfig;
readonly ParameterInfo? parameter;
public void Customize(IFixture fixture)
{
fixture.Customizations.Insert(0, GetSpecimenBuilder());
}
ISpecimenBuilder GetSpecimenBuilder()
{
var specimenBuilder = new HostBubilderUsingServicesSpecimenBuilder(serviceConfig);
if(parameter is null) return specimenBuilder;
var paramSpec = new ParameterSpecification(parameter.ParameterType, parameter.Name);
return new FilteringSpecimenBuilder(specimenBuilder, paramSpec);
}
public HostBubilderUsingServicesCustomization(Action<IServiceCollection> serviceConfig,
ParameterInfo? parameter = null)
{
this.parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
this.serviceConfig = serviceConfig ?? throw new ArgumentNullException(nameof(serviceConfig));
}
}
}

View file

@ -13,9 +13,7 @@ namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
{
public object Create(object request, ISpecimenContext context)
{
if(Equals(request, typeof(IServiceProvider)))
return GetServiceProvider(context);
if(request is ParameterInfo paramInfo && paramInfo.ParameterType == typeof(IServiceProvider))
if(request.IsAnAutofixtureRequestForType<IServiceProvider>())
return GetServiceProvider(context);
return new NoSpecimen();

View file

@ -0,0 +1,23 @@
using System;
using AutoFixture.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
{
public class HostBubilderUsingServicesSpecimenBuilder : ISpecimenBuilder
{
readonly Action<IServiceCollection> serviceConfig;
public object Create(object request, ISpecimenContext context)
=> request.IsAnAutofixtureRequestForType<IHostBuilder>()? GetHostBuilder() : new NoSpecimen();
IHostBuilder GetHostBuilder()
=> Host.CreateDefaultBuilder().ConfigureServices((hostBuilder, services) => serviceConfig(services));
public HostBubilderUsingServicesSpecimenBuilder(Action<IServiceCollection> serviceConfig)
{
this.serviceConfig = serviceConfig;
}
}
}

View file

@ -0,0 +1,29 @@
using System.Reflection;
namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
{
/// <summary>
/// Convenience methods for specimen builders. You're only likey to want to use these extensions
/// within a specimen builder class.
/// </summary>
public static class AutofixtureRequestExtensions
{
/// <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>
/// <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)
{
if(Equals(request, typeof(T)))
return true;
if(request is ParameterInfo paramInfo && paramInfo.ParameterType == typeof(T))
return true;
return false;
}
}
}

View file

@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="NodaTime" Version="3.0.3" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
</ItemGroup>
<ItemGroup>