Skip to content

Commit

Permalink
Refactored Core.Testing to allow tests without Startup class
Browse files Browse the repository at this point in the history
  • Loading branch information
oskardudycz committed May 13, 2021
1 parent ac4dcb5 commit b2a8167
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 53 deletions.
36 changes: 25 additions & 11 deletions Core.Testing/ApiFixture.cs
Original file line number Diff line number Diff line change
@@ -1,55 +1,69 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Core.Events;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

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

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

private HttpClient Client => Sut.Client;

protected abstract string ApiUrl { get; }

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

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

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

protected ApiFixture()
{
Sut = new TestContext<TStartup>(GetConfiguration);
Sut = CreateTestContext();
}

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

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

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

public Task<HttpResponseMessage> GetAsync(string path = "")
public Task<HttpResponseMessage> Get(string path = "")
{
return Client.GetAsync(
$"{ApiUrl}/{path}"
);
}

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

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

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

public IReadOnlyCollection<TEvent> PublishedInternalEventsOfType<TEvent>() where TEvent: IEvent
public IReadOnlyCollection<TEvent> PublishedInternalEventsOfType<TEvent>() where TEvent : IEvent
=> Sut.PublishedInternalEventsOfType<TEvent>();
}
}
72 changes: 47 additions & 25 deletions Core.Testing/TestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,27 @@

namespace Core.Testing
{
public class TestContext<TStartup>: IDisposable
public class TestContext<TStartup>: TestContext
where TStartup : class
{
public TestContext(
Func<string, Dictionary<string, string>>? getConfiguration = null,
Action<IServiceCollection>? setupServices = null,
Func<IWebHostBuilder, IWebHostBuilder>? setupWebHostBuilder = null
): base(getConfiguration, setupServices, (webHostBuilder =>
{
SetupWebHostBuilder(webHostBuilder);
setupWebHostBuilder?.Invoke(webHostBuilder);
return webHostBuilder;
}))
{
}

private static IWebHostBuilder SetupWebHostBuilder(IWebHostBuilder webHostBuilder)
=> webHostBuilder.UseStartup<TStartup>();
}

public class TestContext: IDisposable
{
public HttpClient Client { get; }

Expand All @@ -29,47 +48,50 @@ public class TestContext<TStartup>: IDisposable
private readonly DummyExternalEventProducer externalEventProducer = new();
private readonly DummyExternalCommandBus externalCommandBus = new();

private readonly Func<string, Dictionary<string, string>> getConfiguration = _ => new Dictionary<string, string>();
private readonly Func<string, Dictionary<string, string>> getConfiguration =
_ => new Dictionary<string, string>();

public TestContext(Func<string, Dictionary<string, string>>? getConfiguration = null)
public TestContext(
Func<string, Dictionary<string, string>>? getConfiguration = null,
Action<IServiceCollection>? setupServices = null,
Func<IWebHostBuilder, IWebHostBuilder>? setupWebHostBuilder = null
)
{
if (getConfiguration != null)
{
this.getConfiguration = getConfiguration;
}

var fixtureName = new StackTrace().GetFrame(3)!.GetMethod()!.DeclaringType!.Name;

var configuration = this.getConfiguration(fixtureName);
var projectDir = Directory.GetCurrentDirectory();

server = new TestServer(new WebHostBuilder()
.UseEnvironment("Tests")
.UseContentRoot(projectDir)
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath(projectDir)
.AddJsonFile("appsettings.json", true)
.AddInMemoryCollection(configuration)
.Build()
)
.ConfigureServices(services =>
{
services.AddSingleton(eventsLog);
services.AddSingleton(typeof(INotificationHandler<>), typeof(EventListener<>));
services.AddSingleton<IExternalEventProducer>(externalEventProducer);
services.AddSingleton<IExternalCommandBus>(externalCommandBus);
services.AddSingleton<IExternalEventConsumer, DummyExternalEventConsumer>();
})
.UseStartup<TStartup>());

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


Client = server.CreateClient();
}

public IReadOnlyCollection<TEvent> PublishedExternalEventsOfType<TEvent>() where TEvent: IExternalEvent
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();
}

public IReadOnlyCollection<TCommand> PublishedExternalCommandOfType<TCommand>() where TCommand: ICommand
public IReadOnlyCollection<TCommand> PublishedExternalCommandOfType<TCommand>() where TCommand : ICommand
{
return externalCommandBus.SentCommands.OfType<TCommand>().ToList();
}
Expand Down
33 changes: 33 additions & 0 deletions Core.Testing/TestWebHostBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using Core.Events.External;
using Core.Requests;
using MediatR;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Shipments.Api.Tests.Core;

namespace Core.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("Tests")
.UseContentRoot(projectDir)
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath(projectDir)
.AddJsonFile("appsettings.json", true)
.AddInMemoryCollection(configuration)
.Build()
)
.ConfigureServices(configureServices);
}
}
}
3 changes: 2 additions & 1 deletion Sample/BankAccounts/EventSourcing.Sample.Web/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ private void ConfigureMarten(IServiceCollection services)

private void ConfigureEf(IServiceCollection services)
{
services.AddDbContext<ClientsDbContext>(options => options.UseNpgsql(Configuration.GetConnectionString("ClientsDatabase")));
services.AddDbContext<ClientsDbContext>(
options => options.UseNpgsql(Configuration.GetConnectionString("ClientsDatabase")));
}

public void Configure(IApplicationBuilder app)
Expand Down
4 changes: 2 additions & 2 deletions Sample/ECommerce/Carts/Carts.Api.Tests/Carts/InitCartTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class InitCartFixture: ApiFixture<Startup>

public override async Task InitializeAsync()
{
CommandResponse = await PostAsync(new InitCartRequest {ClientId = ClientId });
CommandResponse = await Post(new InitCartRequest {ClientId = ClientId });
}
}

Expand Down Expand Up @@ -78,7 +78,7 @@ public async Task CreateCommand_ShouldCreate_Cart()
var query = $"{createdId}";

//send query
var queryResponse = await fixture.GetAsync(query);
var queryResponse = await fixture.Get(query);
queryResponse.EnsureSuccessStatusCode();

var queryResult = await queryResponse.Content.ReadAsStringAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class InitOrderFixture: ApiFixture<Startup>

public override async Task InitializeAsync()
{
CommandResponse = await PostAsync(new InitOrderRequest(
CommandResponse = await Post(new InitOrderRequest(
ClientId,
ProductItems,
TotalPrice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class RequestPaymentsTestsFixture: ApiFixture<Startup>

public override async Task InitializeAsync()
{
CommandResponse = await PostAsync(new RequestPaymentRequest {OrderId = OrderId, Amount = Amount});
CommandResponse = await Post(new RequestPaymentRequest {OrderId = OrderId, Amount = Amount});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class SendPackageFixture: ApiFixture<Startup>

public override async Task InitializeAsync()
{
CommandResponse = await PostAsync(new SendPackage(OrderId, ProductItems));
CommandResponse = await Post(new SendPackage(OrderId, ProductItems));
}
}

Expand Down Expand Up @@ -100,7 +100,7 @@ public async Task CreateCommand_ShouldCreate_Package()
var query = $"{createdId}";

//send query
var queryResponse = await fixture.GetAsync(query);
var queryResponse = await fixture.Get(query);
queryResponse.EnsureSuccessStatusCode();

var queryResult = await queryResponse.Content.ReadAsStringAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public override async Task InitializeAsync()
);

// send create command
CommandResponse = await PostAsync(command);
CommandResponse = await Post(command);
}
}

Expand Down Expand Up @@ -80,7 +80,7 @@ public async Task CreateCommand_ShouldUpdateReadModel()
var query = $"{fixture.MeetingId}";

//send query
var queryResponse = await fixture.GetAsync(query);
var queryResponse = await fixture.Get(query);
queryResponse.EnsureSuccessStatusCode();

var queryResult = await queryResponse.Content.ReadAsStringAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public override async Task InitializeAsync()
);

// send create command
await PostAsync( createCommand);
await Post( createCommand);

var occurs = DateRange.Create(Start, End);

// send schedule meeting request
CommandResponse = await PostAsync($"{MeetingId}/schedule", occurs);
CommandResponse = await Post($"{MeetingId}/schedule", occurs);
}
}

Expand Down Expand Up @@ -73,7 +73,7 @@ public async Task ScheduleMeeting_ShouldUpdateReadModel()
var query = new GetMeeting(fixture.MeetingId);

//send query
var queryResponse = await fixture.GetAsync($"{fixture.MeetingId}");
var queryResponse = await fixture.Get($"{fixture.MeetingId}");
queryResponse.EnsureSuccessStatusCode();

var queryResult = await queryResponse.Content.ReadAsStringAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public CreateMeetingTests(CreateMeetingFixture fixture)
public async Task MeetingCreated_ShouldUpdateReadModel()
{
//send query
var queryResponse = await fixture.GetAsync($"{MeetingsSearchApi.MeetingsUrl}");
var queryResponse = await fixture.Get($"{MeetingsSearchApi.MeetingsUrl}");
queryResponse.EnsureSuccessStatusCode();

var queryResult = await queryResponse.Content.ReadAsStringAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected override Dictionary<string, string> GetConfiguration(string fixtureNam
public override async Task InitializeAsync()
{
// send create command
CommandResponse = await PostAsync(new CreateTentativeReservationRequest {SeatId = SeatId});
CommandResponse = await Post(new CreateTentativeReservationRequest {SeatId = SeatId});
}
}

Expand Down Expand Up @@ -86,7 +86,7 @@ public async Task CreateCommand_ShouldCreate_ReservationDetailsReadModel()
var query = $"{createdReservationId}";

//send query
var queryResponse = await fixture.GetAsync(query);
var queryResponse = await fixture.Get(query);
queryResponse.EnsureSuccessStatusCode();

var queryResult = await queryResponse.Content.ReadAsStringAsync();
Expand All @@ -105,7 +105,7 @@ public async Task CreateCommand_ShouldCreate_ReservationList()
var createdReservationId = await fixture.CommandResponse.GetResultFromJson<Guid>();

//send query
var queryResponse = await fixture.GetAsync();
var queryResponse = await fixture.Get();
queryResponse.EnsureSuccessStatusCode();

var queryResult = await queryResponse.Content.ReadAsStringAsync();
Expand Down Expand Up @@ -138,7 +138,7 @@ public async Task CreateCommand_ShouldCreate_ReservationHistory()
var query = $"{createdReservationId}/history";

//send query
var queryResponse = await fixture.GetAsync(query);
var queryResponse = await fixture.Get(query);
queryResponse.EnsureSuccessStatusCode();

var queryResult = await queryResponse.Content.ReadAsStringAsync();
Expand Down

0 comments on commit b2a8167

Please sign in to comment.