Writing tests¶
public Task Marks_order_paid_when_payment_completed_is_consumed() => stove.Test(async t =>
{
var orderId = Guid.NewGuid();
await t.Postgres().Execute("insert into orders (id, status) values (@id, 'Created')", new NpgsqlParameter("id", orderId));
await t.Kafka().Publish("payments.completed", new PaymentCompleted(orderId, "pay-3"));
await t.Kafka().ShouldBeConsumed<PaymentCompleted>(m => m.Value.OrderId == orderId, consumerGroup: "order-service");
await t.Using<OrderRepository>(async repository =>
Assert.Equal("Paid", (await repository.Find(orderId, t.CancellationToken))?.Status));
});
stove.Test(body, cancellationToken)takes an optional token, for example xUnit'sTestContext.Current.CancellationToken, so framework timeouts cancel Stove's waits.- Framework control-flow exceptions (skip, inconclusive, pass) and cancellation pass through unchanged.
Eventually.AssertAsync(() => ..., timeout)retries any assertion, from any assertion library, until it passes.Eventually.UntilAsyncpolls a value instead; an exception from its probe is an error and is not retried.
Modules at a glance¶
- Http:
Get/Post/Put/Patch/Delete<T>(uri, body?, headers?)returnsStoveHttpResponse<T>withStatusCode,Headers,RawBodyand a lazily deserializedBody.SendRaw(HttpRequestMessage)sends a hand-built request. - Postgres:
Execute(sql, params),Query<T>(sql, map, params)andDataSourcefor raw Npgsql access. Options:Migrations,Cleanup,Image,ConfigureContainer,ConfigureDataSource. - SQL Server:
Execute(sql, params),Query<T>(sql, map, params)andOpenConnectionAsync(ct)for native SqlClient access. Options:Migrations,Cleanup,Image,ConfigureContainer,ConfigureConnectionandUseExisting. - MongoDB:
Insert(collection, document),Query<T>(collection, filter)and nativeClient,Database,Collection<T>(). OrderedSetupprepares collections, indexes and seed data. - MySQL:
ExecuteandQuery<T>withMySqlParameterand nativeDataSourceaccess. Options includeMigrations,Cleanup,ConfigureDataSource,ConfigureContainerandUseExisting. - Kafka (observes broker records and committed offsets; propagate correlation headers):
Publish<T>/PublishRawsend a message as the running test.ShouldBePublished<T>waits until a matching message appears on any topic. Stove tails every topic with its own consumer.ShouldNotBePublished<T>(condition, within, topic)observes for the full interval and fails on a matching message. This means no matching message was observed during that window; it cannot rule out later delivery or observer lag.ShouldBeConsumed<T>waits until a consumer group has committed past the matching message.ShouldBeFailed<T>waits until a matching message appears on an error topic (.erroror.DLTby default).PeekAsync<T>returns retained messages of the current test observed so far.Observationbounds evidence per test and excludes uncorrelated records by default. See messaging guarantees.- Serialization uses System.Text.Json by default. Plug in your own with
IStoveKafkaSerde, or useWithKafkaAvro()for generated Avro events. Kafka security, Schema Registry and Avro covers certificate configuration, schema migrations and subject naming. - RabbitMQ:
Publish/PublishRawawait broker confirms;ShouldBePublished,ShouldNotBePublishedandPeekinspect copies on Stove's exclusive queue. ConfigureBindingsand orderedSetup; nativeConnectionis available. Observation does not prove application processing. See setup and guarantees. - Azure Service Bus: typed queues/topics/subscriptions, correlated
Publish/Schedule, non-destructivePeekandShouldBeScheduled, plus destructiveReceivewith explicit Complete/Abandon/DeadLetter/Defer settlement. Existing Azure namespaces accept anyTokenCredential. See authentication, scheduling and parallelism. - Redis:
MultiplexerandDatabase(db), plusMigrationsandCleanupoptions. - WireMock:
MockGet/MockPost/MockPut/MockPatch/MockDelete(path, statusCode, responseBody, requestBody?, headers?, delay?).MockRaw(method, path, statusCode, string or byte[] body, contentType)for XML, text or binary responses.Stub(request => ..., response => ...)uses WireMock.Net's own builders;request.WithPathTemplate(...)is available there.Requests(method?, path?)returnsRecordedRequests withBodyAs<T>(),PathParameters,QueryandHeaders.ShouldHaveBeenCalled(method, path, times, within?)returns the matching requests;ShouldNotHaveBeenCalled.pathis an exact path or an OpenAPI template:/stock/{productId}, or/{bucket}/{key+}for the rest of the path.Servergives raw access.- Stubs created in a test are removed when the test ends.
- Telemetry:
Spans(),Logs(),ShouldContainSpan(predicate),ShouldNotHaveFailedSpans(),RenderTree().
Faking third-party APIs¶
Wrap each third-party API in a small typed fake built from its API definition (OpenAPI/Swagger spec, SDK service model or docs). The fake wraps a named WireMock instance, points the app's client at it through configuration, and offers scenario and verification methods:
// fixture
StoveBuilder.Create().WithInventoryFake().WithPaymentsFake() /* ... */;
// test
t.PaymentsFake().ChargeDeclined();
var charge = await t.PaymentsFake().ShouldHaveCharged(c => c.Amount == 20m);
The example project contains two fakes generated from OpenAPI specs:
Fakes/ from
specs/. The
agent skill teaches coding agents to generate fakes like these, including for SDKs such as AWS S3.