Skip to content

Commit

Permalink
Add MotorHostApplicationFactory to make IMotorStartup testable
Browse files Browse the repository at this point in the history
Fixes #61

Signed-off-by: jan.jansen <jan.jansen@gdata.de>
  • Loading branch information
secana authored and farodin91 committed Dec 10, 2020
1 parent ce57c02 commit 6399c5b
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 60 deletions.
6 changes: 3 additions & 3 deletions examples/ConsumeAndPublishWithRabbitMQ/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@ public static Task Main() =>
MotorHost.CreateDefaultBuilder()
// Configure the types of the input and output messages
.ConfigureSingleOutputService<InputMessage, OutputMessage>()
.ConfigureServices((hostContext, services) =>
.ConfigureServices((_, services) =>
{
// Add a handler for the input message which returns an output message
// This handler is called for every new incoming message
services.AddTransient<ISingleOutputService<InputMessage, OutputMessage>, SingleOutputService>();
})
// Add the incomming communication module.
.ConfigureConsumer<InputMessage>((context, builder) =>
.ConfigureConsumer<InputMessage>((_, builder) =>
{
// In this case the messages are received from RabbitMQ
builder.AddRabbitMQ();
// The encoding of the incoming message, such that the handler is able to deserialize the message
builder.AddSystemJson();
})
// Add the outgoing communication module.
.ConfigurePublisher<OutputMessage>((context, builder) =>
.ConfigurePublisher<OutputMessage>((_, builder) =>
{
// In this case the messages are send to RabbitMQ
builder.AddRabbitMQ();
Expand Down
46 changes: 23 additions & 23 deletions shared.csproj
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
<Project>

<PropertyGroup>
<Version>0.4.0</Version>
<LangVersion>9</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8625;CS8618;CS8604;CS8601</WarningsAsErrors>
</PropertyGroup>
<PropertyGroup>
<Version>0.4.0</Version>
<LangVersion>9</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8625;CS8618;CS8604;CS8601</WarningsAsErrors>
</PropertyGroup>

<PropertyGroup>
<IncludeSymbols>true</IncludeSymbols>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>
<PropertyGroup>
<IncludeSymbols>true</IncludeSymbols>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>

<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Motor.NET is a microservice framework based on Microsoft.Extensions.Hosting.</Description>
<Copyright>Copyright (C) 2020 G DATA CyberDefense AG</Copyright>
<Company>G DATA CyberDefense AG</Company>
<RepositoryUrl>https://github.com/GDATASoftwareAG/motornet</RepositoryUrl>
<PackageProjectUrl>https://github.com/GDATASoftwareAG/motornet</PackageProjectUrl>
<PackageTags>microservice hosting rabbitmq kafka http logging tracing</PackageTags>
<PackageIcon>Icon_Motor_NET_128.png</PackageIcon>
</PropertyGroup>
<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Motor.NET is a microservice framework based on Microsoft.Extensions.Hosting.</Description>
<Copyright>Copyright (C) 2020 G DATA CyberDefense AG</Copyright>
<Company>G DATA CyberDefense AG</Company>
<RepositoryUrl>https://github.com/GDATASoftwareAG/motornet</RepositoryUrl>
<PackageProjectUrl>https://github.com/GDATASoftwareAG/motornet</PackageProjectUrl>
<PackageTags>microservice hosting rabbitmq kafka http logging tracing</PackageTags>
<PackageIcon>Icon_Motor_NET_128.png</PackageIcon>
</PropertyGroup>

<ItemGroup>
<None Include="../../rsc/Icon_Motor_NET_128.png" Pack="true" PackagePath="/" />
</ItemGroup>
<ItemGroup>
<None Include="../../rsc/Icon_Motor_NET_128.png" Pack="true" PackagePath="/" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<IsTestProject>false</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Motor.Extensions.Hosting.Abstractions\Motor.Extensions.Hosting.Abstractions.csproj" />
<ProjectReference Include="..\Motor.Extensions.Utilities.Abstractions\Motor.Extensions.Utilities.Abstractions.csproj" />
<ProjectReference Include="..\Motor.Extensions.Utilities\Motor.Extensions.Utilities.csproj" />
</ItemGroup>

<Import Project="$(MSBuildThisFileDirectory)../../shared.csproj" />
Expand Down
43 changes: 43 additions & 0 deletions src/Motor.Extensions.TestUtilities/MotorHostApplicationFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Motor.Extensions.Utilities;
using Motor.Extensions.Utilities.Abstractions;
using Xunit;

namespace Motor.Extensions.TestUtilities
{
public class MotorHostApplicationFactory<TStartup> : IAsyncLifetime where TStartup : IMotorStartup
{
private TestServer? server;

public async Task InitializeAsync()
{
var useSetting = new Dictionary<string, string>
{
{MotorHostDefaults.EnablePrometheusEndpointKey, false.ToString()}
};

var webHostBuilder = new WebHostBuilder();
MotorHostBuilderHelper.ConfigureWebHost(webHostBuilder, s => useSetting.GetValueOrDefault(s), typeof(TStartup));
webHostBuilder.ConfigureServices(collection =>
{
collection.AddHealthChecks();
});

server = new TestServer(webHostBuilder);
}

public HttpClient CreateClient() => server?.CreateClient()!;

public async Task DisposeAsync()
{
server?.Dispose();
}
}
}
32 changes: 3 additions & 29 deletions src/Motor.Extensions.Utilities/MotorHostBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;
using Motor.Extensions.Diagnostics.HealthChecks;
using Motor.Extensions.Diagnostics.Metrics;
using Motor.Extensions.Utilities.Abstractions;

namespace Motor.Extensions.Utilities
Expand All @@ -17,7 +14,7 @@ public class MotorHostBuilder : IMotorHostBuilder
private readonly IHostBuilder _builder;
private readonly IConfiguration _config;
private readonly bool _enableConfigureWebDefaults;
private readonly List<HealthCheckData> _healthChecks = new List<HealthCheckData>();
private readonly List<HealthCheckData> _healthChecks = new();
private Type? _type;

public MotorHostBuilder(IHostBuilder builder, bool enableConfigureWebDefaults = true)
Expand Down Expand Up @@ -99,31 +96,7 @@ public IHost Build()
_builder
.ConfigureWebHostDefaults(builder =>
{
IMotorStartup? startup = null;
if (_type != null) startup = Activator.CreateInstance(_type) as IMotorStartup;

var urls = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
const string defaultUrl = "http://0.0.0.0:9110";
if (string.IsNullOrEmpty(urls))
builder.UseUrls(defaultUrl);
else if (!urls.Contains(defaultUrl)) builder.UseUrls($"{urls};{defaultUrl}");

builder.Configure((context, applicationBuilder) =>
{
applicationBuilder.UseRouting();
var enablePrometheusSetting = GetSetting(MotorHostDefaults.EnablePrometheusEndpointKey);
if (string.IsNullOrEmpty(enablePrometheusSetting) || bool.Parse(enablePrometheusSetting))
applicationBuilder.UsePrometheusServer();
startup?.Configure(context, applicationBuilder);
applicationBuilder.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/health"); });
});
if (_type != null)
builder.UseSetting(WebHostDefaults.ApplicationKey, _type.Assembly.GetName().Name);

builder.ConfigureServices((context, collection) =>
{
startup?.ConfigureServices(context, collection);
});
MotorHostBuilderHelper.ConfigureWebHost(builder, GetSetting, _type);
})
.ConfigureHealthChecks(builder =>
{
Expand All @@ -140,6 +113,7 @@ public IHost Build()
return _builder.Build();
}


public IDictionary<object, object> Properties => _builder.Properties;

public string? GetSetting(string key)
Expand Down
40 changes: 40 additions & 0 deletions src/Motor.Extensions.Utilities/MotorHostBuilderHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Motor.Extensions.Diagnostics.Metrics;
using Motor.Extensions.Utilities.Abstractions;

namespace Motor.Extensions.Utilities
{
public static class MotorHostBuilderHelper
{
public static void ConfigureWebHost(IWebHostBuilder builder, Func<string, string?> getSetting, Type? motorStartup)
{
IMotorStartup? startup = null;
if (motorStartup != null) startup = Activator.CreateInstance(motorStartup) as IMotorStartup;

var urls = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
const string defaultUrl = "http://0.0.0.0:9110";
if (string.IsNullOrEmpty(urls))
builder.UseUrls(defaultUrl);
else if (!urls.Contains(defaultUrl)) builder.UseUrls($"{urls};{defaultUrl}");

builder.Configure((context, applicationBuilder) =>
{
applicationBuilder.UseRouting();
var enablePrometheusSetting = getSetting(MotorHostDefaults.EnablePrometheusEndpointKey);
if (string.IsNullOrEmpty(enablePrometheusSetting) || bool.Parse(enablePrometheusSetting))
applicationBuilder.UsePrometheusServer();
startup?.Configure(context, applicationBuilder);
applicationBuilder.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/health"); });
});
if (motorStartup != null)
builder.UseSetting(WebHostDefaults.ApplicationKey, motorStartup.Assembly.GetName().Name);

builder.ConfigureServices((context, collection) =>
{
startup?.ConfigureServices(context, collection);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public async void ExecuteAsync_MultipleMessage_ParallelProcessingBasedOnConfig(
int? parallelProcessesOrProcessorCount)
{
parallelProcessesOrProcessorCount ??= Environment.ProcessorCount;
var waitTimeInMs = 100;
var waitTimeInMs = 200;
var taskCompletionSources = new List<TaskCompletionSource<ProcessedMessageStatus>>();
var queue = new Mock<IBackgroundTaskQueue<MotorCloudEvent<string>>>();
var setupSequentialResult = queue.SetupSequence(t => t.DequeueAsync(It.IsAny<CancellationToken>()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Motor.Extensions.TestUtilities;
using Motor.Extensions.Utilities.Abstractions;
using Xunit;

namespace Motor.Extensions.Utilities_IntegrationTest
{
public class TestService
{
}

public class HomeController : Controller
{
private TestService service;

public HomeController(TestService testService)
{
service = testService ?? throw new ArgumentNullException();
}

//
// GET: /
public string Index()
{
return "This is my default action...";
}
}

public class TestStartup : IMotorStartup
{
public void ConfigureServices(WebHostBuilderContext context, IServiceCollection services)
{
services.AddControllers();
services.AddTransient<TestService>();
}

public void Configure(WebHostBuilderContext context, IApplicationBuilder app)
{
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}


public class DefaultHostingTest : IClassFixture<MotorHostApplicationFactory<TestStartup>>
{
private readonly MotorHostApplicationFactory<TestStartup> _factory;

public DefaultHostingTest(MotorHostApplicationFactory<TestStartup> factory)
{
_factory = factory;
}

[Theory]
[InlineData("/")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();

// Act
var response = await client.GetAsync(url);

// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,16 @@ private static IHost GetReverseStringService()
{
var host = MotorHost.CreateDefaultBuilder()
.ConfigureSingleOutputService<string, string>()
.ConfigureServices((hostContext, services) =>
.ConfigureServices((_, services) =>
{
services.AddTransient<ISingleOutputService<string, string>, ReverseStringConverter>();
})
.ConfigureConsumer<string>((context, builder) =>
.ConfigureConsumer<string>((_, builder) =>
{
builder.AddRabbitMQ();
builder.AddDeserializer<StringDeserializer>();
})
.ConfigurePublisher<string>((context, builder) =>
.ConfigurePublisher<string>((_, builder) =>
{
builder.AddRabbitMQ();
builder.AddSerializer<StringSerializer>();
Expand Down

0 comments on commit 6399c5b

Please sign in to comment.