From ca15475789a2492daf04c655126ff5c84b94d405 Mon Sep 17 00:00:00 2001 From: James Randall Date: Sat, 16 Mar 2019 13:23:46 +0000 Subject: [PATCH] Test framework now includes validators --- .../AbstractAcceptanceTest.cs | 3 +- .../AcceptanceTestScaffold.cs | 25 +++++--- .../FunctionMonkey.Testing.csproj | 7 +-- .../TestFunctionHostBuilder.cs | 1 + .../ValidatingDispatcher.cs | 60 +++++++++++++++++++ .../ValidationException.cs | 15 +++++ 6 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 Source/FunctionMonkey.Testing/ValidatingDispatcher.cs create mode 100644 Source/FunctionMonkey.Testing/ValidationException.cs diff --git a/Source/FunctionMonkey.Testing/AbstractAcceptanceTest.cs b/Source/FunctionMonkey.Testing/AbstractAcceptanceTest.cs index 46276887..0bac94cf 100644 --- a/Source/FunctionMonkey.Testing/AbstractAcceptanceTest.cs +++ b/Source/FunctionMonkey.Testing/AbstractAcceptanceTest.cs @@ -80,7 +80,8 @@ public virtual void AfterBuild(IServiceCollection serviceCollection, ICommandReg public IServiceProvider ServiceProvider => _scaffold.ServiceProvider; /// - /// A convenience property to provide easy access to the registered ICommandDispatcher + /// Provides access to the command dispatcher registered in the service provider but wrapped + /// in a decorator that implements validation. /// public ICommandDispatcher Dispatcher => _scaffold.Dispatcher; } diff --git a/Source/FunctionMonkey.Testing/AcceptanceTestScaffold.cs b/Source/FunctionMonkey.Testing/AcceptanceTestScaffold.cs index bf4213e9..154c6e36 100644 --- a/Source/FunctionMonkey.Testing/AcceptanceTestScaffold.cs +++ b/Source/FunctionMonkey.Testing/AcceptanceTestScaffold.cs @@ -2,7 +2,6 @@ using System; using System.IO; using System.Reflection; -using System.Threading; using AzureFromTheTrenches.Commanding; using AzureFromTheTrenches.Commanding.Abstractions; using FunctionMonkey.Abstractions; @@ -19,7 +18,7 @@ namespace FunctionMonkey.Testing public class AcceptanceTestScaffold { private static bool _environmentVariablesRegistered = false; - private static object _loadingAppVariablesLock = new object(); + private static readonly object LoadingAppVariablesLock = new object(); /// /// Setup the scaffold with the default TestFunctionHostBuilder @@ -77,12 +76,10 @@ public void Setup( beforeBuild?.Invoke(serviceCollection, commandRegistry); functionAppConfiguration.Build(testFunctionHostBuilder); - + afterBuild?.Invoke(serviceCollection, commandRegistry); ServiceProvider = serviceCollection.BuildServiceProvider(); - - Dispatcher = ServiceProvider.GetService(); } /// @@ -94,6 +91,7 @@ protected virtual void RegisterFunctionMonkeyMocks(IServiceCollection serviceCol serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); } /// @@ -131,7 +129,7 @@ public void AddEnvironmentVariables(string appSettingsPath, bool oneTimeOnly = t private static void SetEnvironmentVariables(Stream appSettings, bool oneTimeOnly) { - lock (_loadingAppVariablesLock) + lock (LoadingAppVariablesLock) { if (_environmentVariablesRegistered && oneTimeOnly) { @@ -171,9 +169,20 @@ private static void SetEnvironmentVariables(Stream appSettings, bool oneTimeOnly public IServiceProvider ServiceProvider { get; private set; } /// - /// A convenience property to provide easy access to the registered ICommandDispatcher + /// Provides access to the command dispatcher registered in the service provider but wrapped + /// in a decorator that implements validation. /// - public ICommandDispatcher Dispatcher { get; private set; } + public ICommandDispatcher Dispatcher + { + get + { + //IValidator validator = ServiceProvider.GetService(); + //ICommandDispatcher registeredDispatcher = ServiceProvider.GetService(); + //ValidatingDispatcher validatingDispatcher = new ValidatingDispatcher(registeredDispatcher, validator); + ValidatingDispatcher validatingDispatcher = ServiceProvider.GetService(); + return validatingDispatcher; + } + } } } diff --git a/Source/FunctionMonkey.Testing/FunctionMonkey.Testing.csproj b/Source/FunctionMonkey.Testing/FunctionMonkey.Testing.csproj index 63d13eef..cfc600ec 100644 --- a/Source/FunctionMonkey.Testing/FunctionMonkey.Testing.csproj +++ b/Source/FunctionMonkey.Testing/FunctionMonkey.Testing.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 1.0.0 + 1.1.3 James Randall James Randall @@ -10,8 +10,8 @@ https://github.com/JamesRandall/FunctionMonkey.git https://functionmonkey.azurefromthetrenches.com/ true - 1.0.0.0 - 1.0.0.0 + 1.1.3.0 + 1.1.3.0 @@ -19,7 +19,6 @@ - diff --git a/Source/FunctionMonkey.Testing/TestFunctionHostBuilder.cs b/Source/FunctionMonkey.Testing/TestFunctionHostBuilder.cs index 621eaa28..a32cd3e4 100644 --- a/Source/FunctionMonkey.Testing/TestFunctionHostBuilder.cs +++ b/Source/FunctionMonkey.Testing/TestFunctionHostBuilder.cs @@ -42,6 +42,7 @@ public IFunctionHostBuilder DefaultHttpResponseHandler() where public IFunctionHostBuilder AddValidator() where TValidator : IValidator { + _serviceCollection.AddTransient(typeof(IValidator), typeof(TValidator)); return this; } diff --git a/Source/FunctionMonkey.Testing/ValidatingDispatcher.cs b/Source/FunctionMonkey.Testing/ValidatingDispatcher.cs new file mode 100644 index 00000000..dc7c37dd --- /dev/null +++ b/Source/FunctionMonkey.Testing/ValidatingDispatcher.cs @@ -0,0 +1,60 @@ +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using AzureFromTheTrenches.Commanding.Abstractions; +using AzureFromTheTrenches.Commanding.Abstractions.Model; +using FunctionMonkey.Abstractions.Validation; +using FunctionMonkey.Commanding.Abstractions.Validation; + +namespace FunctionMonkey.Testing +{ + public class ValidatingDispatcher : ICommandDispatcher + { + private readonly ICommandDispatcher _underlyingDispatcher; + private readonly IValidator _validator; + + public ValidatingDispatcher(ICommandDispatcher underlyingDispatcher, IValidator validator) + { + _underlyingDispatcher = underlyingDispatcher; + _validator = validator; + } + + public async Task> DispatchAsync(ICommand command, CancellationToken cancellationToken = new CancellationToken()) + { + Validate(command); + + return await _underlyingDispatcher.DispatchAsync(command, cancellationToken); + } + + public async Task DispatchAsync(ICommand command, CancellationToken cancellationToken = new CancellationToken()) + { + Validate(command); + return await _underlyingDispatcher.DispatchAsync(command, cancellationToken); + } + + public ICommandExecuter AssociatedExecuter { get; } + + private void Validate(ICommand command) + { + // The .Validate method uses its generic parameter to determine the type of the + // command as it uses generics with the IServiceCollecton, this has the unfortunate + // side-effect of meaning you can't ask it to validate a none-concretely typed ICommand + // This isn't an issue in Function Monkey itself as we have the specific command type + // but does cause a problem here as if we call .Validate(command) command will be resolved + // as being of type ICommand and so no validator will be found. + // + // To work round this we construct a method call using the concrete type of the command. + // In test code this isn't performance sensitive so we don't bother compiling an expression + // for it. + MethodInfo methodInfo = _validator.GetType().GetMethod("Validate"); + MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(command.GetType()); + ValidationResult validationResult = (ValidationResult)genericMethodInfo.Invoke(_validator, new [] {command}); + + //ValidationResult validationResult = _validator.Validate(command); + if (!validationResult.IsValid) + { + throw new ValidationException(validationResult); + } + } + } +} diff --git a/Source/FunctionMonkey.Testing/ValidationException.cs b/Source/FunctionMonkey.Testing/ValidationException.cs new file mode 100644 index 00000000..7d0fd590 --- /dev/null +++ b/Source/FunctionMonkey.Testing/ValidationException.cs @@ -0,0 +1,15 @@ +using System; +using FunctionMonkey.Commanding.Abstractions.Validation; + +namespace FunctionMonkey.Testing +{ + public class ValidationException : Exception + { + public ValidationException(ValidationResult validationResult) + { + ValidationResult = validationResult; + } + + public ValidationResult ValidationResult { get; } + } +}