Skip to content

Commit

Permalink
Untangled Core.WebApi and Core.Api.Testing from main Core domain proj…
Browse files Browse the repository at this point in the history
…ect to enable also samples using other patterns
  • Loading branch information
oskardudycz committed Sep 29, 2021
1 parent e833db9 commit c70f599
Show file tree
Hide file tree
Showing 36 changed files with 310 additions and 175 deletions.
97 changes: 97 additions & 0 deletions Core.Api.Testing/ApiFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Core.Api.Testing
{
public abstract class ApiFixture<TStartup>: ApiFixture where TStartup : class
{
public override TestContext CreateTestContext() =>
new TestContext<TStartup>(GetConfiguration, SetupServices, SetupWebHostBuilder);
}

public abstract class ApiFixture: IAsyncLifetime
{
protected readonly TestContext Sut;

protected HttpClient Client => Sut.Client;

protected TestServer Server => Sut.Server;

protected abstract string ApiUrl { get; }

protected virtual Dictionary<string, string> GetConfiguration(string fixtureName) => new();

protected virtual Action<IServiceCollection>? SetupServices => null;

protected virtual Func<IWebHostBuilder, IWebHostBuilder>? SetupWebHostBuilder => null;

protected ApiFixture()
{
Environment.SetEnvironmentVariable("SchemaName", GetType().Name.ToLower());

Sut = CreateTestContext();
}

public virtual TestContext CreateTestContext() => new(GetConfiguration, SetupServices, SetupWebHostBuilder);

public virtual Task InitializeAsync() => Task.CompletedTask;

public virtual Task DisposeAsync() => Task.CompletedTask;

public async Task<HttpResponseMessage> Get(string path = "", int maxNumberOfRetries = 0, int retryIntervalInMs = 1000, Func<HttpResponseMessage, ValueTask<bool>>? check = null)
{
HttpResponseMessage queryResponse;
var retryCount = maxNumberOfRetries;

var doCheck = check ?? (response => new (response.StatusCode == HttpStatusCode.OK));
do
{
queryResponse = await Client.GetAsync(
$"{ApiUrl}/{path}"
);

if (retryCount == 0 || (await doCheck(queryResponse)))
break;

await Task.Delay(retryIntervalInMs);
retryCount--;
} while (true);
return queryResponse;
}

public Task<HttpResponseMessage> Post(string path, object request)
{
return Client.PostAsync(
$"{ApiUrl}/{path}",
request.ToJsonStringContent()
);
}

public Task<HttpResponseMessage> Post(object request)
{
return Post(string.Empty, request);
}

public Task<HttpResponseMessage> Put(string path, object? request = null)
{
return Client.PutAsync(
$"{ApiUrl}/{path}",
request != null ?
request.ToJsonStringContent()
: new StringContent(string.Empty)
);
}

public Task<HttpResponseMessage> Put(object request)
{
return Put(string.Empty, request);
}
}
}
19 changes: 19 additions & 0 deletions Core.Api.Testing/Core.Api.Testing.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsTestProject>false</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.8" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Threading.Tasks;
using FluentAssertions;

namespace Core.Testing
namespace Core.Api.Testing
{
public static class ResponseExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Text;
using Newtonsoft.Json;

namespace Core.Testing
namespace Core.Api.Testing
{
public static class SerializationExtensions
{
Expand Down
53 changes: 6 additions & 47 deletions Core.Testing/TestContext.cs → Core.Api.Testing/TestContext.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Core.Commands;
using Core.Events;
using Core.Events.External;
using Core.Requests;
using MediatR;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Shipments.Api.Tests.Core;

namespace Core.Testing
namespace Core.Api.Testing
{
public class TestContext<TStartup>: TestContext
where TStartup : class
Expand All @@ -40,11 +32,7 @@ public class TestContext: IDisposable
{
public HttpClient Client { get; }

private readonly TestServer server;

private readonly EventsLog eventsLog = new();
private readonly DummyExternalEventProducer externalEventProducer = new();
private readonly DummyExternalCommandBus externalCommandBus = new();
public readonly TestServer Server;

private readonly Func<string, Dictionary<string, string>> getConfiguration =
_ => new Dictionary<string, string>();
Expand All @@ -65,53 +53,24 @@ public TestContext(
var configuration = this.getConfiguration(fixtureName);

setupWebHostBuilder ??= webHostBuilder => webHostBuilder;
server = new TestServer(setupWebHostBuilder(TestWebHostBuilder.Create(configuration, services =>
Server = new TestServer(setupWebHostBuilder(TestWebHostBuilder.Create(configuration, services =>
{
ConfigureTestServices(services);
setupServices?.Invoke(services);
})));


Client = server.CreateClient();
}

protected void ConfigureTestServices(IServiceCollection services)
{
services.AddSingleton(eventsLog);
services.AddSingleton(typeof(INotificationHandler<>), typeof(EventListener<>));
services.AddSingleton<IExternalEventProducer>(externalEventProducer);
services.AddSingleton<IExternalCommandBus>(externalCommandBus);
services.AddSingleton<IExternalEventConsumer, DummyExternalEventConsumer>();
}

public IReadOnlyCollection<TEvent> PublishedExternalEventsOfType<TEvent>() where TEvent : IExternalEvent
{
return externalEventProducer.PublishedEvents.OfType<TEvent>().ToList();
Client = Server.CreateClient();
}

public IReadOnlyCollection<TCommand> PublishedExternalCommandOfType<TCommand>() where TCommand : ICommand
protected virtual void ConfigureTestServices(IServiceCollection services)
{
return externalCommandBus.SentCommands.OfType<TCommand>().ToList();
}

public async Task PublishInternalEvent(IEvent @event)
{
using (var scope = server.Host.Services.CreateScope())
{
var eventBus = scope.ServiceProvider.GetRequiredService<IEventBus>();
await eventBus.Publish(@event);
}
}

public void Dispose()
{
server.Dispose();
Server.Dispose();
Client.Dispose();
}

public IReadOnlyCollection<TEvent> PublishedInternalEventsOfType<TEvent>()
{
return eventsLog.PublishedEvents.OfType<TEvent>().ToList();
}
}
}
29 changes: 29 additions & 0 deletions Core.Api.Testing/TestWebHostBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Core.Api.Testing
{
public static class TestWebHostBuilder
{
public static IWebHostBuilder Create(Dictionary<string, string> configuration, Action<IServiceCollection>? configureServices = null)
{
var projectDir = Directory.GetCurrentDirectory();
configureServices ??= _ => { };

return new WebHostBuilder()
.UseEnvironment("Development")
.UseContentRoot(projectDir)
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath(projectDir)
.AddJsonFile("appsettings.json", true)
.AddInMemoryCollection(configuration)
.Build()
)
.ConfigureServices(configureServices);
}
}
}
93 changes: 34 additions & 59 deletions Core.Testing/ApiFixture.cs
Original file line number Diff line number Diff line change
@@ -1,86 +1,61 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Linq;
using System.Threading.Tasks;
using Core.Api.Testing;
using Core.Commands;
using Core.Events;
using Microsoft.AspNetCore.Hosting;
using Core.Events.External;
using Core.Requests;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Shipments.Api.Tests.Core;

namespace Core.Testing
{
public abstract class ApiFixture<TStartup>: ApiFixture where TStartup : class
public abstract class ApiWithEventsFixture<TStartup>: Api.Testing.ApiFixture<TStartup> where TStartup : class
{
public override TestContext CreateTestContext() =>
new TestContext<TStartup>(GetConfiguration, SetupServices, SetupWebHostBuilder);
}

public abstract class ApiFixture: IAsyncLifetime
{
protected readonly TestContext Sut;

private HttpClient Client => Sut.Client;
private readonly EventsLog eventsLog = new();
private readonly DummyExternalEventProducer externalEventProducer = new();
private readonly DummyExternalCommandBus externalCommandBus = new();

protected abstract string ApiUrl { get; }
public override TestContext CreateTestContext() =>
new TestContext<TStartup>(GetConfiguration, (services) =>
{
SetupServices?.Invoke(services);
services.AddSingleton(eventsLog);
services.AddSingleton(typeof(INotificationHandler<>), typeof(EventListener<>));
services.AddSingleton<IExternalEventProducer>(externalEventProducer);
services.AddSingleton<IExternalCommandBus>(externalCommandBus);
services.AddSingleton<IExternalEventConsumer, DummyExternalEventConsumer>();
protected virtual Dictionary<string, string> GetConfiguration(string fixtureName) => new();
}, SetupWebHostBuilder);

protected virtual Action<IServiceCollection>? SetupServices => null;

protected virtual Func<IWebHostBuilder, IWebHostBuilder>? SetupWebHostBuilder => null;

protected ApiFixture()
public IReadOnlyCollection<TEvent> PublishedExternalEventsOfType<TEvent>() where TEvent : IExternalEvent
{
Environment.SetEnvironmentVariable("SchemaName", GetType().Name.ToLower());

Sut = CreateTestContext();
return externalEventProducer.PublishedEvents.OfType<TEvent>().ToList();
}

public virtual TestContext CreateTestContext() => new(GetConfiguration, SetupServices, SetupWebHostBuilder);

public virtual Task InitializeAsync() => Task.CompletedTask;

public virtual Task DisposeAsync() => Task.CompletedTask;

public async Task<HttpResponseMessage> Get(string path = "", int maxNumberOfRetries = 0, int retryIntervalInMs = 1000, Func<HttpResponseMessage, ValueTask<bool>>? check = null)
public IReadOnlyCollection<TCommand> PublishedExternalCommandOfType<TCommand>() where TCommand : ICommand
{
HttpResponseMessage queryResponse;
var retryCount = maxNumberOfRetries;

var doCheck = check ?? (response => new (response.StatusCode == HttpStatusCode.OK));
do
{
queryResponse = await Client.GetAsync(
$"{ApiUrl}/{path}"
);

if (retryCount == 0 || (await doCheck(queryResponse)))
break;

await Task.Delay(retryIntervalInMs);
retryCount--;
} while (true);
return queryResponse;
return externalCommandBus.SentCommands.OfType<TCommand>().ToList();
}

public Task<HttpResponseMessage> Post(string path, object request)
public async Task PublishInternalEvent(IEvent @event)
{
return Client.PostAsync(
$"{ApiUrl}/{path}",
request.ToJsonStringContent()
);
using var scope = Server.Host.Services.CreateScope();
var eventBus = scope.ServiceProvider.GetRequiredService<IEventBus>();
await eventBus.Publish(@event);
}

public Task<HttpResponseMessage> Post(object request)
public IReadOnlyCollection<TEvent> PublishedInternalEventsOfType<TEvent>()
{
return Post(string.Empty, request);
return eventsLog.PublishedEvents.OfType<TEvent>().ToList();
}
}

public IReadOnlyCollection<TEvent> PublishedExternalEventsOfType<TEvent>() where TEvent : IExternalEvent
=> Sut.PublishedExternalEventsOfType<TEvent>();

public IReadOnlyCollection<TEvent> PublishedInternalEventsOfType<TEvent>() where TEvent : IEvent
=> Sut.PublishedInternalEventsOfType<TEvent>();
public abstract class ApiWithEventsFixture: Api.Testing.ApiFixture
{
}
}
1 change: 1 addition & 0 deletions Core.Testing/Core.Testing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Core.Api.Testing\Core.Api.Testing.csproj" />
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
</Project>
4 changes: 0 additions & 4 deletions Core.WebApi/Core.WebApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
Expand Down
Loading

0 comments on commit c70f599

Please sign in to comment.