WIP composing customize attributes
This isn't really related to #751 - it's a refactor of some of our test logic, to avoid bloat of semi-repeated autofixture customization attributes. By switching to a behaviour-driven model, it's possible to compose them, which means that there's no need to part-duplicate between them. It should also reduce the overall number of them, as combinations can be put together on-demand, rather than needing a new attribute of their own.
This commit is contained in:
parent
ef117eced7
commit
d285b8b505
|
|
@ -1,83 +0,0 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Elsa.Core.IntegrationTests.Workflows;
|
||||
using Elsa.Persistence.MongoDb.Extensions;
|
||||
using Elsa.Testing.Shared.AutoFixture.Customizations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Elsa.Persistence.EntityFramework.Core.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Elsa.Persistence.EntityFramework.Sqlite;
|
||||
using Elsa.Persistence.YesSql;
|
||||
using YesSql.Provider.Sqlite;
|
||||
using System.Data;
|
||||
using Elsa.Testing.Shared.Helpers;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class HostBuilderWithDuplicateActivitiesWorkflowAttribute : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return new HostBuilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa.AddWorkflow<DuplicateActivitiesWorkflow>();
|
||||
});
|
||||
}, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public class HostBuilderWithDuplicateActivitiesWorkflowAndMongoDbAttribute : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return new HostBuilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa.AddWorkflow<DuplicateActivitiesWorkflow>();
|
||||
elsa.UseMongoDbPersistence(opts => {
|
||||
opts.ConnectionString = "mongodb://localhost:27017";
|
||||
opts.DatabaseName = "IntegrationTests";
|
||||
});
|
||||
});
|
||||
}, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public class HostBuilderWithDuplicateActivitiesWorkflowAndEntityFrameworkAttribute : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
var tempFolder = new TemporaryFolder();
|
||||
|
||||
return new HostBuilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa
|
||||
.AddWorkflow<DuplicateActivitiesWorkflow>()
|
||||
.UseEntityFrameworkPersistence(opts => {
|
||||
opts.UseSqlite($"Data Source={tempFolder.Folder}elsa.db;", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name));
|
||||
});
|
||||
});
|
||||
}, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public class HostBuilderWithDuplicateActivitiesWorkflowAndYesSqlAttribute : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return new HostBuilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa
|
||||
.AddWorkflow<DuplicateActivitiesWorkflow>()
|
||||
.UseYesSqlPersistence(config => {
|
||||
config.UseSqLite("Data Source=elsa-sqlite.db;", IsolationLevel.ReadUncommitted);
|
||||
});
|
||||
});
|
||||
}, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Elsa.Core.IntegrationTests.Workflows;
|
||||
using Elsa.Persistence.MongoDb.Extensions;
|
||||
using Elsa.Testing.Shared.AutoFixture.Customizations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Elsa.Persistence.EntityFramework.Core.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Elsa.Persistence.EntityFramework.Sqlite;
|
||||
using Elsa.Persistence.YesSql;
|
||||
using YesSql.Provider.Sqlite;
|
||||
using Elsa.Testing.Shared.Helpers;
|
||||
using Elsa.Testing.Shared.AutoFixture.Attributes;
|
||||
using System;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class WithDuplicateActivitiesWorkflowAttribute : ElsaHostBuilderBuilderCustomizeAttributeBase
|
||||
{
|
||||
public override Action<ElsaHostBuilderBuilder> GetBuilderCustomizer()
|
||||
{
|
||||
return builder => {
|
||||
builder.ElsaCallbacks.Add(elsa => {
|
||||
elsa.AddWorkflow<DuplicateActivitiesWorkflow>();
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using Elsa.Testing.Shared.AutoFixture.Attributes;
|
||||
using Elsa.Testing.Shared.Helpers;
|
||||
using Elsa.Persistence.EntityFramework.Core.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Elsa.Persistence.EntityFramework.Sqlite;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class WithEntityFrameworkAttribute : ElsaHostBuilderBuilderCustomizeAttributeBase
|
||||
{
|
||||
public override Action<ElsaHostBuilderBuilder> GetBuilderCustomizer()
|
||||
{
|
||||
var tempFolder = new TemporaryFolder();
|
||||
|
||||
return builder => {
|
||||
builder.ElsaCallbacks.Add(elsa => {
|
||||
elsa.UseEntityFrameworkPersistence(opts => {
|
||||
opts.UseSqlite($"Data Source={tempFolder.GetContainedPath("elsa.db")};", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name));
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using Elsa.Persistence.MongoDb.Extensions;
|
||||
using Elsa.Testing.Shared.AutoFixture.Attributes;
|
||||
using Elsa.Testing.Shared.Helpers;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class WithMongoDbAttribute : ElsaHostBuilderBuilderCustomizeAttributeBase
|
||||
{
|
||||
public override Action<ElsaHostBuilderBuilder> GetBuilderCustomizer()
|
||||
{
|
||||
return builder => {
|
||||
builder.ElsaCallbacks.Add(elsa => {
|
||||
elsa.UseMongoDbPersistence(opts => {
|
||||
opts.ConnectionString = "mongodb://localhost:27017";
|
||||
opts.DatabaseName = "IntegrationTests";
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Elsa.Testing.Shared.Helpers;
|
||||
using Elsa.Testing.Shared.AutoFixture.Attributes;
|
||||
using System;
|
||||
using Elsa.Persistence.YesSql;
|
||||
using YesSql.Provider.Sqlite;
|
||||
using System.Data;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class WithSqliteYesSqlAttribute : ElsaHostBuilderBuilderCustomizeAttributeBase
|
||||
{
|
||||
public override Action<ElsaHostBuilderBuilder> GetBuilderCustomizer()
|
||||
{
|
||||
var tempFolder = new TemporaryFolder();
|
||||
|
||||
return builder => {
|
||||
builder.ElsaCallbacks.Add(elsa => {
|
||||
elsa.UseYesSqlPersistence(config => {
|
||||
config.UseSqLite($"Data Source={tempFolder.GetContainedPath("elsa.db")};", IsolationLevel.ReadUncommitted);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using System.Threading;
|
|||
using Elsa.Services;
|
||||
using Elsa.Core.IntegrationTests.Workflows;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Testing.Shared.Helpers;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Persistence
|
||||
{
|
||||
|
|
@ -20,35 +21,33 @@ namespace Elsa.Core.IntegrationTests.Persistence
|
|||
*/
|
||||
|
||||
[Theory(DisplayName = "A workflow that contains duplicate activities may be run & persisted to an in-memory store"), AutoMoqData]
|
||||
public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableInMemory([HostBuilderWithDuplicateActivitiesWorkflow] IHostBuilder hostBuilder)
|
||||
public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableInMemory([WithDuplicateActivitiesWorkflow] ElsaHostBuilderBuilder hostBuilderBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<DuplicateActivitiesWorkflow>>();
|
||||
});
|
||||
var host = await hostBuilder.StartAsync();
|
||||
await PerformTest(hostBuilderBuilder);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "A workflow that contains duplicate activities may be run & persisted to an EF Sqlite store"), AutoMoqData]
|
||||
public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithEntityFramework([HostBuilderWithDuplicateActivitiesWorkflowAndEntityFramework] IHostBuilder hostBuilder)
|
||||
public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithEntityFramework([WithDuplicateActivitiesWorkflow,WithEntityFramework] ElsaHostBuilderBuilder hostBuilderBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<DuplicateActivitiesWorkflow>>();
|
||||
});
|
||||
var host = await hostBuilder.StartAsync();
|
||||
await PerformTest(hostBuilderBuilder);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "A workflow that contains duplicate activities may be run & persisted to a MongoDb store"), AutoMoqData]
|
||||
public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithMongoDb([HostBuilderWithDuplicateActivitiesWorkflowAndMongoDb] IHostBuilder hostBuilder)
|
||||
public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithMongoDb([WithDuplicateActivitiesWorkflow,WithMongoDb] ElsaHostBuilderBuilder hostBuilderBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<DuplicateActivitiesWorkflow>>();
|
||||
});
|
||||
var host = await hostBuilder.StartAsync();
|
||||
await PerformTest(hostBuilderBuilder);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "A workflow that contains duplicate activities may be run & persisted to a YesSQL store"), AutoMoqData]
|
||||
public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithYesSql([HostBuilderWithDuplicateActivitiesWorkflowAndYesSql] IHostBuilder hostBuilder)
|
||||
public async Task ADuplicateActivitiesWorkflowInstanceShouldBeRoundTrippableWithYesSql([WithDuplicateActivitiesWorkflow,WithSqliteYesSql] ElsaHostBuilderBuilder hostBuilderBuilder)
|
||||
{
|
||||
await PerformTest(hostBuilderBuilder);
|
||||
}
|
||||
|
||||
async Task PerformTest(ElsaHostBuilderBuilder hostBuilderBuilder)
|
||||
{
|
||||
var hostBuilder = hostBuilderBuilder.GetHostBuilder();
|
||||
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<DuplicateActivitiesWorkflow>>();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Kernel;
|
||||
using AutoFixture.Xunit2;
|
||||
using Elsa.Testing.Shared.AutoFixture.Specifications;
|
||||
using Elsa.Testing.Shared.Helpers;
|
||||
|
||||
namespace Elsa.Testing.Shared.AutoFixture.Attributes
|
||||
{
|
||||
public abstract class ElsaHostBuilderBuilderCustomizeAttributeBase : CustomizeAttribute
|
||||
{
|
||||
public abstract Action<ElsaHostBuilderBuilder> GetBuilderCustomizer();
|
||||
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
=> new ElsaHostBuilderBuilderCustomization(GetBuilderCustomizer());
|
||||
|
||||
class ElsaHostBuilderBuilderCustomization : ICustomization
|
||||
{
|
||||
readonly Action<ElsaHostBuilderBuilder> builderCustomizer;
|
||||
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Behaviors.Add(new ElsaHostBuilderBuilderTransformation(builderCustomizer));
|
||||
}
|
||||
|
||||
public ElsaHostBuilderBuilderCustomization(Action<ElsaHostBuilderBuilder> builderCustomizer)
|
||||
{
|
||||
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
|
||||
}
|
||||
}
|
||||
|
||||
class ElsaHostBuilderBuilderTransformation : ISpecimenBuilderTransformation
|
||||
{
|
||||
readonly Action<ElsaHostBuilderBuilder> builderCustomizer;
|
||||
|
||||
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
|
||||
{
|
||||
return new Postprocessor(builder, new ElsaHostBuilderBuilderCommand(builderCustomizer), new IsAnElsaHostBuilderBuilderSpec());
|
||||
}
|
||||
|
||||
public ElsaHostBuilderBuilderTransformation(Action<ElsaHostBuilderBuilder> builderCustomizer)
|
||||
{
|
||||
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
|
||||
}
|
||||
}
|
||||
|
||||
class ElsaHostBuilderBuilderCommand : ISpecimenCommand
|
||||
{
|
||||
readonly Action<ElsaHostBuilderBuilder> builderCustomizer;
|
||||
|
||||
public void Execute(object specimen, ISpecimenContext context)
|
||||
{
|
||||
var builder = (ElsaHostBuilderBuilder) specimen;
|
||||
builderCustomizer(builder);
|
||||
}
|
||||
|
||||
public ElsaHostBuilderBuilderCommand(Action<ElsaHostBuilderBuilder> builderCustomizer)
|
||||
{
|
||||
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Elsa.Testing.Shared.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class which 'builds' an <see cref="IHostBuilder"/> for testing purposes.
|
||||
/// The reason this is useful is that it also allows building-up callbacks to occur inside
|
||||
/// the <see cref="ElsaServiceCollectionExtensions.AddElsa"/> callback (which cannot be
|
||||
/// done with a normal host builder alone).
|
||||
/// </summary>
|
||||
public class ElsaHostBuilderBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a collection of the callbacks to be executed upon the <see cref="IServiceCollection"/> of
|
||||
/// the host builder as it is created.
|
||||
/// </summary>
|
||||
/// <returns>The services callbacks</returns>
|
||||
public IList<Action<IServiceCollection>> ServiceCallbacks { get; } = new List<Action<IServiceCollection>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the callbacks to be executed upon the <see cref="ElsaOptions"/> of
|
||||
/// the host builder's services as it is created.
|
||||
/// </summary>
|
||||
/// <returns>The Elsa callbacks</returns>
|
||||
public IList<Action<ElsaOptions>> ElsaCallbacks { get; } = new List<Action<ElsaOptions>>();
|
||||
|
||||
Action<ElsaOptions> ElsaConfiguration
|
||||
=> ElsaCallbacks
|
||||
.Where(x => !(x is null))
|
||||
.Aggregate(EmptyElsaAction, (acc, next) => o => { acc(o); next(o); });
|
||||
|
||||
Action<IServiceCollection> ServiceConfiguration
|
||||
=> ServiceCallbacks
|
||||
.Where(x => !(x is null))
|
||||
.Aggregate(EmptyServicesAction, (acc, next) => o => { acc(o); next(o); });
|
||||
|
||||
static Action<ElsaOptions> EmptyElsaAction => o => {};
|
||||
|
||||
static Action<IServiceCollection> EmptyServicesAction => s => {};
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IHostBuilder"/> configured using the <see cref="ServiceCallbacks"/>
|
||||
/// and <see cref="ElsaCallbacks"/>.
|
||||
/// </summary>
|
||||
/// <returns>A host builder.</returns>
|
||||
public IHostBuilder GetHostBuilder()
|
||||
{
|
||||
return Host.CreateDefaultBuilder()
|
||||
.ConfigureServices((hostBuilder, services) => {
|
||||
services.AddElsa(ElsaConfiguration);
|
||||
ServiceConfiguration(services);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,13 @@ namespace Elsa.Testing.Shared.Helpers
|
|||
|
||||
public string Folder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a path string for a file or folder which is contained within the <see cref="Folder"/>.
|
||||
/// </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 void Dispose()
|
||||
{
|
||||
if (_deleteOnDispose)
|
||||
|
|
|
|||
Loading…
Reference in a new issue