Для завершения в ES6 у нас есть классы (поддерживаемые на момент написания этого только в последних браузерах, но доступные в Babel, TypeScript и других транспилерах)
class Foo {
constructor(){
this.a = 5;
this.b = 6;
this.c = this.a + this.b;
}
}
const foo = new Foo();
Я посмотрел код хоста функции Azure и нашел этот фрагмент кода в файле Program.cs
:
var host = new HostBuilder()
.SetAzureFunctionsEnvironment()
.ConfigureLogging(b =>
{
b.SetMinimumLevel(LogLevel.Information);
b.AddConsole();
})
.AddScriptHost(options, webJobsBuilder =>
{
webJobsBuilder.AddAzureStorageCoreServices();
})
.UseConsoleLifetime()
.Build();
Часть, которая меня заинтересовала, была AddScriptHost()
метод расширения, который делает доступным экземпляр webJobsBuilder
(реализацию IWebJobsBuilder
).
Зная это, я создал следующий метод, который создает простой экземпляр IHost
и использует мой существующий класс Startup
, который содержит все внедренные сервисы:
/// <summary>
/// Builds an instance of the specified <typeparamref name="TFunctionType"/>
/// with the services defined in the <paramref name="startup"/> instance.
/// </summary>
/// <typeparam name="TFunctionType"></typeparam>
/// <param name="startup"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException">
/// Thrown if:
/// - The <paramref name="startup" /> instance is not specified.
/// </exception>
public static TFunctionType Instanciate<TFunctionType>(Startup startup)
{
Argument.ThrowIfIsNull(startup, nameof(startup));
// --> Builds an IHost with all the services registered in the Startup.
IHost host = new HostBuilder().ConfigureWebJobs(startup.Configure).Build();
return Instanciate<TFunctionType>(host);
}
Метод Instanciate<TFunctionType>
ищет конструктор TFunctionType
и извлекает все сервисы из экземпляра IHost
:
/// <summary>
/// Instanciates the specified <typeparamref name="TFunctionType"></typeparamref>.
/// </summary>
/// <typeparam name="TFunctionType"></typeparam>
/// <param name="host"></param>
/// <returns></returns>
private static TFunctionType Instanciate<TFunctionType>(IHost host)
{
Type type = typeof(TFunctionType);
// --> This part could be better...
ConstructorInfo contructorInfo = type.GetConstructors().FirstOrDefault();
ParameterInfo[] parametersInfo = contructorInfo.GetParameters();
object[] parameters = LookupServiceInstances(host, parametersInfo);
return (TFunctionType) Activator.CreateInstance(type, parameters);
}
/// <summary>
/// Gets all the parameters instances from the host's services.
/// </summary>
/// <param name="host"></param>
/// <param name="parametersInfo"></param>
/// <returns></returns>
private static object[] LookupServiceInstances(IHost host, IReadOnlyList<ParameterInfo> parametersInfo)
{
return parametersInfo.Select(p => host.Services.GetService(p.ParameterType))
.ToArray();
}
Я поместил эти методы в класс HostHelper
. Теперь в моем тесте я могу повторно использовать класс Startup
.
Более того, я могу создать подкласс Startup
, чтобы смоделировать фрагменты кода, которые используют какой-то ввод-вывод, чтобы сделать мои интеграционные тесты более устойчивыми:
public class CreateAccountFunctionTests
{
private readonly CreateAccountFunction m_creationAccountFunction;
public CreateAccountFunctionTests()
{
var startup = new Startup();
m_creationAccountFunction = HostHelper.Instanciate<CreateAccountFunction>(startup);
}
[Fact]
public void TestSomething()
{
// Arrange.
HttpRequest httpRequest = /* builds an instance of HttpRequest */
// Act.
var result = m_creationAccountFunction.Run(httpRequest);
// Assert.
// Asserts the Status Code.
}
}