-
-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3a7466b
commit f02c900
Showing
10 changed files
with
496 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
Source/FunctionMonkey.Testing/AbstractAcceptanceTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
using System; | ||
using System.IO; | ||
using AzureFromTheTrenches.Commanding.Abstractions; | ||
using FunctionMonkey.Abstractions.Builders; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace FunctionMonkey.Testing | ||
{ | ||
/// <summary> | ||
/// A class that can be used as a basis for running acceptance tests with Function Monkey at the command dispatch level | ||
/// designed for use with test frameworks that take a constructor approach to | ||
/// | ||
/// One of the advantages of the pattern used by Function Monkey is that the host function triggers are separated cleanly | ||
/// from business logic and compiled with pre-tested templates allowing for comprehensive acceptance tests to be run | ||
/// just below this level which can often provide a high level of value with a lower level of complexity than also testing | ||
/// the Function triggers. | ||
/// | ||
/// Typically the none-generic version of this class is more useful but this version allows for a custom IFunctionHostBuilder | ||
/// to be used if additional functionality is required over and above that baked into the supplied TestFunctionHostBuilder which | ||
/// solely handles command registration and dependency injection management. | ||
/// </summary> | ||
public abstract class AbstractAcceptanceTest<TFunctionHostBuilder> where TFunctionHostBuilder : class, IFunctionHostBuilder | ||
{ | ||
private readonly AcceptanceTestScaffold _scaffold = new AcceptanceTestScaffold(); | ||
|
||
protected AbstractAcceptanceTest() | ||
{ | ||
_scaffold.Setup<TFunctionHostBuilder>(null, BeforeBuild, AfterBuild); | ||
} | ||
|
||
/// <summary> | ||
/// Set up environment variables based on a settings.json file in a stream | ||
/// </summary> | ||
/// <param name="appSettings"></param> | ||
protected void AddEnvironmentVariables(Stream appSettings, bool oneTimeOnly=true) | ||
{ | ||
_scaffold.AddEnvironmentVariables(appSettings, oneTimeOnly); | ||
} | ||
|
||
/// <summary> | ||
/// Set up environment variables based on a settings.json filename | ||
/// </summary> | ||
/// <param name="appSettingsPath"></param> | ||
protected void AddEnvironmentVariables(string appSettingsPath, bool oneTimeOnly = true) | ||
{ | ||
_scaffold.AddEnvironmentVariables(appSettingsPath, oneTimeOnly); | ||
} | ||
|
||
/// <summary> | ||
/// This method can be used to modify dependency and command setup before the Function App Configuration | ||
/// builder has been run and before tests are run. | ||
/// | ||
/// This must not access members (and should not need to) as it is invoked from the constructor to | ||
/// support test frameworks such as XUnit that construct test cases this way. | ||
/// </summary> | ||
/// <param name="serviceCollection"></param> | ||
/// <param name="commandRegistry"></param> | ||
public virtual void BeforeBuild(IServiceCollection serviceCollection, ICommandRegistry commandRegistry) | ||
{ | ||
|
||
} | ||
|
||
/// <summary> | ||
/// This method can be used to modify dependency and command setup after the Function App Configuration | ||
/// builder has been run and before tests are run. | ||
/// | ||
/// This must not access members (and should not need to) as it is invoked from the constructor to | ||
/// support test frameworks such as XUnit that construct test cases this way. | ||
/// </summary> | ||
/// <param name="serviceCollection"></param> | ||
/// <param name="commandRegistry"></param> | ||
public virtual void AfterBuild(IServiceCollection serviceCollection, ICommandRegistry commandRegistry) | ||
{ | ||
|
||
} | ||
|
||
/// <summary> | ||
/// The constructed service provider | ||
/// </summary> | ||
public IServiceProvider ServiceProvider => _scaffold.ServiceProvider; | ||
|
||
/// <summary> | ||
/// A convenience property to provide easy access to the registered ICommandDispatcher | ||
/// </summary> | ||
public ICommandDispatcher Dispatcher => _scaffold.Dispatcher; | ||
} | ||
|
||
/// <summary> | ||
/// A class that can be used as a basis for running acceptance tests with Function Monkey at the command dispatch level. | ||
/// | ||
/// One of the advantages of the pattern used by Function Monkey is that the host function triggers are separated cleanly | ||
/// from business logic and compiled with pre-tested templates allowing for comprehensive acceptance tests to be run | ||
/// just below this level which can often provide a high level of value with a lower level of complexity than also testing | ||
/// the Function triggers. | ||
/// </summary> | ||
public abstract class AbstractAcceptanceTest : AbstractAcceptanceTest<TestFunctionHostBuilder> | ||
{ | ||
|
||
} | ||
} |
167 changes: 167 additions & 0 deletions
167
Source/FunctionMonkey.Testing/AcceptanceTestScaffold.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
using FunctionMonkey.Testing.Mocks; | ||
using System; | ||
using System.IO; | ||
using System.Reflection; | ||
using System.Threading; | ||
using AzureFromTheTrenches.Commanding; | ||
using AzureFromTheTrenches.Commanding.Abstractions; | ||
using FunctionMonkey.Abstractions; | ||
using FunctionMonkey.Abstractions.Builders; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Newtonsoft.Json.Linq; | ||
|
||
namespace FunctionMonkey.Testing | ||
{ | ||
/// <summary> | ||
/// A scaffold class that can be used to set up Function Monkey acceptance tests designed for test frameworks that | ||
/// make use of setup and teardown methods. | ||
/// </summary> | ||
public class AcceptanceTestScaffold | ||
{ | ||
private static int _environmentVariablesRegistered = 0; | ||
|
||
/// <summary> | ||
/// Setup the scaffold with the default TestFunctionHostBuilder | ||
/// </summary> | ||
/// <param name="beforeBuild">An optional function to run before the Build method is called on the Function App configuration</param> | ||
/// <param name="afterBuild">An optional function to run before the Build method is called after the Function App configuration</param> | ||
/// <param name="functionAppConfigurationAssembly">If your Function App Configuration cannot be found you may need to provide the assembly it is located within to the setup - this is due to the as needed dependency loader and that a method setup based test may not yet have needed the required assembly.</param> | ||
public void Setup( | ||
Assembly functionAppConfigurationAssembly = null, | ||
Action<IServiceCollection, ICommandRegistry> beforeBuild = null, | ||
Action<IServiceCollection, ICommandRegistry> afterBuild = null) | ||
{ | ||
Setup<TestFunctionHostBuilder>(functionAppConfigurationAssembly, beforeBuild, afterBuild); | ||
} | ||
|
||
/// <summary> | ||
/// Setup the scaffold with a IFunctionHostBuilder | ||
/// </summary> | ||
/// <param name="beforeBuild">An optional function to run before the Build method is called on the Function App configuration</param> | ||
/// <param name="afterBuild">An optional function to run before the Build method is called after the Function App configuration</param> | ||
/// /// <param name="functionAppConfigurationAssembly">If your Function App Configuration cannot be found you may need to provide the assembly it is located within to the setup - this is due to the as needed dependency loader and that a method setup based test may not yet have needed the required assembly.</param> | ||
public void Setup<TFunctionHostBuilder>( | ||
Assembly functionAppConfigurationAssembly = null, | ||
Action<IServiceCollection, ICommandRegistry> beforeBuild = null, | ||
Action<IServiceCollection, ICommandRegistry> afterBuild = null | ||
) | ||
where TFunctionHostBuilder : class, IFunctionHostBuilder | ||
{ | ||
IServiceCollection serviceCollection = new ServiceCollection(); | ||
RegisterFunctionMonkeyMocks(serviceCollection); | ||
CommandingDependencyResolverAdapter adapter = new CommandingDependencyResolverAdapter( | ||
(fromType, toInstance) => serviceCollection.AddSingleton(fromType, toInstance), | ||
(fromType, toType) => serviceCollection.AddTransient(fromType, toType), | ||
(resolveType) => ServiceProvider.GetService(resolveType) | ||
); | ||
|
||
// We register the commanding runtime on a per test / thread basis rather than globally | ||
// as multiple tests running concurrently could be undertaking setup at the same time | ||
// (we want this to be isolate) | ||
CommandingRuntime commandingRuntime = new CommandingRuntime(); | ||
ICommandRegistry commandRegistry = commandingRuntime.AddCommanding(adapter); | ||
|
||
IFunctionAppConfiguration functionAppConfiguration = functionAppConfigurationAssembly != null | ||
? ConfigurationLocator.FindConfiguration(functionAppConfigurationAssembly) | ||
: ConfigurationLocator.FindConfiguration(); | ||
|
||
IFunctionHostBuilder testFunctionHostBuilder = | ||
(TFunctionHostBuilder) Activator.CreateInstance( | ||
typeof(TFunctionHostBuilder), | ||
BindingFlags.Default, | ||
null, | ||
new object[] {serviceCollection, commandRegistry}, | ||
null); | ||
|
||
beforeBuild?.Invoke(serviceCollection, commandRegistry); | ||
|
||
functionAppConfiguration.Build(testFunctionHostBuilder); | ||
|
||
afterBuild?.Invoke(serviceCollection, commandRegistry); | ||
|
||
ServiceProvider = serviceCollection.BuildServiceProvider(); | ||
|
||
Dispatcher = ServiceProvider.GetService<ICommandDispatcher>(); | ||
} | ||
|
||
/// <summary> | ||
/// Registers the internal dependencies of the Function Monkey runtime | ||
/// </summary> | ||
/// <param name="serviceCollection"></param> | ||
protected virtual void RegisterFunctionMonkeyMocks(IServiceCollection serviceCollection) | ||
{ | ||
serviceCollection.AddTransient<ICommandClaimsBinder, CommandClaimsBinderMock>(); | ||
serviceCollection.AddTransient<IContextSetter, ContextManagerMock>(); | ||
serviceCollection.AddTransient<IContextProvider, ContextManagerMock>(); | ||
} | ||
|
||
/// <summary> | ||
/// Add environment variables from a stream containing a Functions appsettings file | ||
/// </summary> | ||
/// <param name="appSettingsStream">The app settings stream</param> | ||
/// <param name="oneTimeOnly">Defaults to true, if true only set the variables one time</param> | ||
public void AddEnvironmentVariables(Stream appSettingsStream, bool oneTimeOnly = true) | ||
{ | ||
if (Interlocked.Exchange(ref _environmentVariablesRegistered, 1) == 1) | ||
{ | ||
return; | ||
} | ||
|
||
SetEnvironmentVariables(appSettingsStream); | ||
} | ||
|
||
/// <summary> | ||
/// Add environment variables from a file containing a Functions appsettings file | ||
/// </summary> | ||
/// <param name="appSettingsPath">A path to the app settings file</param> | ||
/// <param name="oneTimeOnly">Defaults to true, if true only set the variables one time</param> | ||
public void AddEnvironmentVariables(string appSettingsPath, bool oneTimeOnly = true) | ||
{ | ||
if (Interlocked.Exchange(ref _environmentVariablesRegistered, 1) == 1) | ||
{ | ||
return; | ||
} | ||
|
||
using (Stream stream = new FileStream(appSettingsPath, FileMode.Open, FileAccess.Read, FileShare.Read)) | ||
{ | ||
SetEnvironmentVariables(stream); | ||
} | ||
} | ||
|
||
private static void SetEnvironmentVariables(Stream appSettings) | ||
{ | ||
string json; | ||
using (StreamReader reader = new StreamReader(appSettings)) | ||
{ | ||
json = reader.ReadToEnd(); | ||
} | ||
|
||
if (!string.IsNullOrWhiteSpace(json)) | ||
{ | ||
JObject settings = JObject.Parse(json); | ||
JObject values = (JObject)settings["Values"]; | ||
if (values != null) | ||
{ | ||
foreach (JProperty property in values.Properties()) | ||
{ | ||
if (property.Value != null) | ||
{ | ||
Environment.SetEnvironmentVariable(property.Name, property.Value.ToString()); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// The constructed service provider | ||
/// </summary> | ||
public IServiceProvider ServiceProvider { get; private set; } | ||
|
||
/// <summary> | ||
/// A convenience property to provide easy access to the registered ICommandDispatcher | ||
/// </summary> | ||
public ICommandDispatcher Dispatcher { get; private set; } | ||
} | ||
} | ||
|
26 changes: 26 additions & 0 deletions
26
Source/FunctionMonkey.Testing/FunctionMonkey.Testing.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<Version>0.21.6-beta000</Version> | ||
<Authors>James Randall</Authors> | ||
<Company>James Randall</Company> | ||
<Copyright></Copyright> | ||
<PackageLicenseUrl>https://raw.githubusercontent.com/JamesRandall/AzureFromTheTrenches.Commanding/master/LICENSE</PackageLicenseUrl> | ||
<RepositoryUrl>https://github.com/JamesRandall/FunctionMonkey.git</RepositoryUrl> | ||
<PackageProjectUrl>https://functionmonkey.azurefromthetrenches.com/</PackageProjectUrl> | ||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | ||
<AssemblyVersion>0.21.6.0</AssemblyVersion> | ||
<FileVersion>0.21.6.0</FileVersion> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\FunctionMonkey.Abstractions\FunctionMonkey.Abstractions.csproj" /> | ||
<ProjectReference Include="..\FunctionMonkey\FunctionMonkey.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
14 changes: 14 additions & 0 deletions
14
Source/FunctionMonkey.Testing/Mocks/CommandClaimsBinderMock.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System.Security.Claims; | ||
using AzureFromTheTrenches.Commanding.Abstractions; | ||
using FunctionMonkey.Abstractions; | ||
|
||
namespace FunctionMonkey.Testing.Mocks | ||
{ | ||
public class CommandClaimsBinderMock : ICommandClaimsBinder | ||
{ | ||
public bool Bind(ClaimsPrincipal principal, ICommand command) | ||
{ | ||
return false; | ||
} | ||
} | ||
} |
Oops, something went wrong.