In reactive domain, there is a lot of utilities for testing against fake event stores. Due to this, there is an ability to create a near 1-1 realistic system test to ensure the pieces of the system come together and work as expected.
You can start this by building a WebApplicationFactory<TStartup>
class, and within the ConfigureWebHost
method, implement the ConfigureTestServices
to include the mock event store connection.
public class ApiApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class {
public static TestTimeSource TimeSource { get; } = new TestTimeSource(); // from ReactiveDomain
public static IClock Clock { get; } = new Clock(); // implement similar in your solution to control "time" within the system.
protected override void ConfigureWebHost(IWebHostBuilder builder) {
// mock your environment variables...
Environment.SetEnvironmentVariable("key", "value");
// this is the setup for Serilog. Setup and otherwise create your logging tools
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
//.WriteTo.Console()
.WriteTo.Debug()
.CreateLogger();
builder.UseSerilog(Log.Logger);
LogManager.SetLogFactory((_) => new ConsoleLogger());
builder.ConfigureTestServices(services => {
// Here's the magic.
// you'll resolve the services as implemented in your actual system.
var configuredConnectionDescriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(IConfiguredConnection));
// you remove the service configuration.
services.Remove(configuredConnectionDescriptor);
// now you create and add the mock configuration that will be used, in this instance, to prove the
// interaction with your event store is operational. This is in-memory, so it allows the system
// to move fast, and be quickly disposed of. A set of smoke tests could be run (or this could be
// overloaded to *NOT* put the mock repository in place (e.g. using the environment variables aboce))
services.AddSingleton<IConfiguredConnection>(svc => {
var conn = new ReactiveDomain.Testing.EventStore.MockStreamStoreConnection("Test Fixture");
conn.Connect();
var configured = new ConfiguredConnection(
conn,
new PrefixedCamelCaseStreamNameBuilder("gift"),
new JsonMessageSerializer()
);
Populate.AccountData(configured);
return configured;
});
// Remove the ITimeSource that is used "live"
var timeSourceDescriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(ITimeSource));
services.Remove(timeSourceDescriptor);
// Adds the "test" time source, so we can control time in respect to the LaterService
services.AddSingleton<ITimeSource>((ctx) => TimeSource);
// Removes our IClock that is used "live"
var clockDescriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(IClock));
services.Remove(clockDescriptor);
// Adds our testing clock so we can control the clock within the system.
services.AddSingleton<IClock>((ctx) => Clock);
});
}
}
With this in place, if you’re making Web API calls, there are times where you need to wait for a particular IMessage
to be emitted onto the main bus of your system. This can be done using a TestQueue
within the test.
In the project i’m currently working on, I needed a way to ensure that after a period of time, an event was emitted to stop a future command to succeed. At the time, I had expected that I would need a dedicated service to manage this. Chris, however, talked to me about the LaterService, as it simplified the solution by at least an order of magnitude. This is a code snippet from the project showing how to implement and use the TestQueue.
"A delay send envelope is emitted".x(async () => {
IDispatcher dispatcher = _factory.Services.GetRequiredService<IDispatcher>();
using (var tq = new TestQueue(dispatcher, new []{typeof(DelaySendEnvelope)})) {
var request = new RequestOptions<Some_Request> {
RequestTimeout = TimeSpan.FromSeconds(90),
Message = new Some_Request {
... setup your request to the api endpoint.
}
};
var response = await _client.RunAsync(request); // this is just a wrapper over an HttpClient.
response.ShouldSatisfyAllConditions(
r=>r.Status.ShouldBe(ResponseStatus.Approved)
);
tq.WaitFor<DelaySendEnvelope>(TimeSpan.FromMilliseconds(100)); // this is the magic. we can observe the backend queue/dispatcher to ensure the envelope is emitted before continuing to advance the clock and test the timeout.
}
});