The single test-support package for the entire CoreEx ecosystem. One <PackageReference> covers events, outbox, Service Bus, caching, validation, HTTP, and all assertion helpers.
<PackageReference Include="CoreEx.UnitTesting" Version="..." />No additional CoreEx test packages are needed — this package covers everything.
[TestFixture]
public class OrderServiceTest : UnitTestBase
{
[OneTimeSetUp]
public async Task OneTimeSetUp()
{
await Test.ClearFusionCacheAsync().ConfigureAwait(false);
// seed your test database here
}
[Test]
public void Create_Order_Published_To_Outbox()
=> Test.ScopedType<OrderService>()
.ExpectChangeLogCreated()
.ExpectIdentifier()
.ExpectSqlServerOutboxEvents(new CloudEvent { ... })
.Run(s => s.CreateAsync(new Order { ... }));
}Use the database-specific expectation method that matches your domain's persistence provider. Do not mix SQL Server and PostgreSQL helpers.
// SQL Server outbox
.ExpectSqlServerOutboxEvents(new CloudEvent { Subject = "contoso.orders.order.created.v1", ... })
.ExpectNoSqlServerOutboxEvents()
// PostgreSQL outbox
.ExpectPostgresOutboxEvents(new CloudEvent { Subject = "contoso.orders.order.created.v1", ... })
.ExpectNoPostgresOutboxEvents()
// Azure Service Bus direct publisher (no outbox)
.ExpectAzureServiceBusEvents(new CloudEvent { ... })
.ExpectNoAzureServiceBusEvents()// Value-carrying events (Create/Update): reconstructs the expected event payload from the tester's own
// returned value (AssertArgs.Value).
.ExpectSqlServerOutboxEvents(e => e.AssertWithValue("contoso", "contoso.orders.order.created.v1"))
// AssertWithValue factory overload (rare): supply the expected payload directly instead of relying on the
// tester's returned value - for a host-less GenericTester<T> (no IValueExpectations<TValue>, so there is no
// AssertArgs.Value to reconstruct from), or when the published event's payload legitimately differs from what
// the operation returns.
.ExpectSqlServerOutboxEvents(e => e.AssertWithValue(() => expectedPayload, "contoso", "contoso.orders.order.created.v1"))
// No-value events (Delete, or any 204 No Content): there is no returned value to reconstruct a payload from at
// all, so AssertMetadata compares metadata only - destination + title/subject + the key (e.g. the deleted id).
.ExpectSqlServerOutboxEvents(e => e.AssertMetadata("contoso", "contoso.orders.order.deleted", deletedId))Reach for the plain AssertWithValue(destination, subject) first for value-carrying events; only use the valueFactory overload when the returned value genuinely isn't the event's payload. Use AssertMetadata for no-value events — AssertWithValue has nothing to reconstruct from in that case.
// Assert validator passes
await ProductValidator.Default.AssertSuccessAsync(new Product { Sku = "SKU001" });
// Assert validator fails with specific field errors
await ProductValidator.Default.AssertErrorsAsync(
new Product { Sku = "" },
("Sku", "Sku is required."));// Use NUnit's Assert.Throws — NOT AwesomeAssertions' Should().Throw() — to assert that an
// Expect*/Assert* check itself correctly fails (e.g. writing a test for a validator's negative path).
Assert.Throws<AssertionException>(() => Test.Http<Order>()
.ExpectIdentifier()
.Run(HttpMethod.Post, "api/orders", invalidOrder)
.AssertCreated());// Subscribe host
[TestFixture]
public class OrderSubscriberTest : UnitTestBase
{
[Test]
public void Receive_OrderCreated()
=> Test.Type<OrderCreatedSubscriber>()
.ExpectNoSqlServerOutboxEvents()
.Run(s => s.ReceiveAsync(CreateCloudEvent("contoso.orders.order.created.v1", order)));
}// Load seed data from embedded YAML with token substitution
var data = await JsonDataReader.ParseYamlAsync("Resources/data.yaml");
await db.SeedAsync(data);Test.ScopedType<OrderService>()
.WithUser("test@contoso.com")
.Run(s => s.GetAsync(id));- Do not add separate per-feature test packages (e.g.
CoreEx.UnitTesting.Events) — they do not exist; all test helpers are in this package. - Do not use
ExpectSqlServerOutboxEventsfor a PostgreSQL domain or vice versa. - Do not call
PublishAsync()in tests — theEventPublisherDecorator(registered byUseExpectedEventPublisher) captures events automatically. - Do not forget
await Test.ClearFusionCacheAsync()in[OneTimeSetUp]for tests involving cached reference data. - Do not use FluentAssertions — the CoreEx test framework uses AwesomeAssertions (
AwesomeAssertionsNuGet package). - Do not wrap an
Expect*/Assert*check inAction act = () => ...; act.Should().Throw<Exception>();to test that it fails — UnitTestEx'sImplementor.AssertFailmarks the NUnit test as failed the moment it fires, regardless of whether your code later catches the resulting exception. Use NUnit'sAssert.Throws<AssertionException>(...)instead (see "Testing That an Expectation Fails" above).
- README — full expectations, outbox helpers,
JsonDataReader, andUnitTestExExtensionsAPI reference. - UnitTestEx — the underlying test-host framework.
- AwesomeAssertions — fluent assertion library used internally.
- Testing — comprehensive real-world guide covering unit, integration, API, Subscribe, and Relay test patterns with concrete examples from the sample solution.
- Patterns — test-specific patterns including outbox assertion, mock HTTP client, inter-domain mock strategy, and
FusionCachereset.