GM.Testing.Messaging
1.0.0
dotnet add package GM.Testing.Messaging --version 1.0.0
NuGet\Install-Package GM.Testing.Messaging -Version 1.0.0
<PackageReference Include="GM.Testing.Messaging" Version="1.0.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="GM.Testing.Messaging" Version="1.0.0" />
<PackageReference Include="GM.Testing.Messaging"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add GM.Testing.Messaging --version 1.0.0
#r "nuget: GM.Testing.Messaging, 1.0.0"
#:package GM.Testing.Messaging@1.0.0
#addin nuget:?package=GM.Testing.Messaging&version=1.0.0
#tool nuget:?package=GM.Testing.Messaging&version=1.0.0
GM.Testing
Shared testing utilities for the GM.* ecosystem — WebApplicationFactory helpers, in-memory fakes of
the GM infrastructure abstractions, published-event capture, and terse HTTP assertions, so tests for
services built on GM.* packages don't need real infra.
Test-only. Every package here is marked DevelopmentDependency (see below), so it
never flows as a transitive runtime dependency. Reference these from test projects only.
Package structure — one broad package vs. several thin ones
You asked me to flag this trade-off. GM.Testing consumes many GM.* packages, so a single package would force every test project — even one unit-testing a pure domain function — to drag in EF Core, Testcontainers (Docker), ASP.NET's test host, Wolverine, and every GM abstraction transitively. NuGet has no "optional dependency": a package's dependencies are always transitive. So bundling is the wrong default.
Decision: split by concern, mirroring how the ecosystem already splits (.Redis / .Http /
.Mediator / .AspNetCore). Each package carries only the dependencies its concern needs:
| Package | Gives you | Heavy deps it pulls | Status |
|---|---|---|---|
| GM.Testing | HTTP assertion helpers (status + body in one call) | none (framework only) | ✅ built |
| GM.Testing.AspNetCore | GmWebApplicationFactory<TProgram> |
Microsoft.AspNetCore.Mvc.Testing |
✅ built |
| GM.Testing.Fakes | In-memory ICacheService / IDistributedLock / IFileStorageService |
GM.Caching / .DistributedLock / .FileStorage (all light) | ✅ built |
| GM.Testing.Messaging | Published-event capture for GM.Messaging | WolverineFx | ✅ built |
| GM.Testing.Mediator | Invoke handlers directly / strip pipeline behaviors | GM.Mediator | ✅ built |
| GM.Testing.EntityFramework | Ephemeral DB fixtures (Testcontainers + EF) | Testcontainers, EF Core | ✅ built |
Plus the dependency-free test data builders (TestDataBuilder<T>) that ship in the core package.
The split that matters most is isolating GM.Testing.EntityFramework — Testcontainers pulls a Docker client and EF pulls a provider; a fast unit-test project should never inherit those just to use a cache fake. The core stays dependency-free so it's safe to reference anywhere.
The one judgement call: the three lightweight fakes share GM.Testing.Fakes rather than one package
each (GM.Testing.Caching, …). They're tiny, single-dependency doubles, and a test project usually
touches several, so bundling them keeps the package count sane. If you later want a caching-only test
project to avoid pulling GM.Messaging/FileStorage, they can split — the types wouldn't change.
GM.Testing — HTTP assertions
var order = await client.GetAsync("/orders/1").Result.ShouldBeOkAsync<OrderDto>(); // 200 + body
await (await client.PostAsJsonAsync("/orders", req)).ShouldBeCreatedAsync(); // 201
await (await client.GetAsync("/orders/absent")).ShouldBeNotFoundAsync(); // 404
On a status mismatch the failure message includes the response body, so a red test tells you why the server rejected the request. No test-framework dependency — works under xUnit/NUnit/MSTest.
GM.Testing.AspNetCore — WebApplicationFactory
GmWebApplicationFactory<TProgram> is a reusable test-host base for GM.API-based services (or any
minimal-API / MVC app) with a fluent surface for the three things every integration test needs:
using var factory = new GmWebApplicationFactory<Program>()
.WithEnvironment("Testing")
.WithConfig("ConnectionStrings:Db", "…") // in-memory config overrides
.WithServices(s => s.AddGMTestingFakes()); // test DI overrides (win over the app's registrations)
var client = factory.CreateClient();
ConfigureTestServices runs after the app's own registration, so .WithServices / .ReplaceService
overrides always win — that's how you swap a real cache/lock/storage for a fake.
GM.Testing.Fakes — in-memory doubles
services.AddGMTestingFakes(); // all three, replacing real registrations
// or individually: AddFakeCache() / AddFakeDistributedLock() / AddFakeFileStorage()
FakeCacheService(ICacheService) — deterministic, single-flightGetOrCreateAsync, honours absolute/sliding TTL against an injectableTimeProvider(fast-forward a test clock to prove expiry). InspectCount/Keys/TryPeek.FakeDistributedLock(IDistributedLock) — real in-process mutual exclusion so lock-guarded code can be tested under contention, orAlwaysAcquire = truefor a no-op lock. InspectHeldResources.FakeFileStorageService(IFileStorageService) — byte-array backed; honoursExpectedChecksum(SHA-256 →ChecksumMismatchException) and not-found semantics. InspectKeys/GetBytes.
GM.Testing.Messaging — published-event capture
services.AddCapturingMessageBus(); // replaces Wolverine's IMessageBus
// …exercise the handler, then:
var bus = provider.GetRequiredService<CapturingMessageBus>();
var evt = bus.ShouldHavePublished<OrderPlacedEvent>(); // asserts exactly one, returns it
Assert.Equal(orderId, evt.OrderId);
⚠️ Flag — hand-rolled Wolverine fake
GM.Messaging publishes through Wolverine's IMessageBus, a large (18-member), fast-moving third-party
interface. CapturingMessageBus implements it by hand — capturing publish/send/invoke and no-op'ing
the routing/streaming surface. That's a maintenance cost: a major Wolverine upgrade can change the
interface and require an update here (the package pins WolverineFx 6.24.2). The alternative is
Wolverine's own in-memory testing (IHost.TrackActivity() / a stub transport), which is
upgrade-proof but couples your test to a running Wolverine host. This package favours the terse,
broker-free, host-free capture; reach for Wolverine's tracking when you need full routing fidelity.
<a name="test-only"></a>Keeping it test-only
Every package sets <DevelopmentDependency>true</DevelopmentDependency>, which stamps
developmentDependency="true" into the nuspec. NuGet then gives it PrivateAssets="all" semantics in
consumers, so if a project references GM.Testing it won't flow to that project's downstream
consumers — a production package can't accidentally ship a testing dependency. The discipline still
holds: reference GM.Testing.* from test projects only.
GM.Testing.Mediator — handler test helpers
// Bypass the whole pipeline — exercise just the handler:
var result = await provider.InvokeHandlerAsync<PlaceOrder, OrderResult>(new PlaceOrder(...));
// Or run through the mediator with selected behaviors stripped:
services.AddGMMediator(assembly);
services.RemovePipelineBehaviors(); // all of them, or…
services.RemovePipelineBehavior(typeof(IdempotencyBehavior<,>)); // …just this one
So unit tests skip idempotency/validation/logging while integration tests keep the full pipeline.
GM.Testing.EntityFramework — ephemeral DB fixtures
Generic over any DbContext (including a GM.EntityFramework.Persistence GenericDbContext-derived one).
// Real Postgres via Testcontainers (needs Docker) — applies migrations on start, tears down on dispose:
await using var db = new PostgresDatabaseFixture<AppDbContext>(opts => new AppDbContext(opts));
await db.InitializeAsync();
await using var ctx = db.CreateContext();
// Docker-free fallback for fast unit tests (EF in-memory provider):
await using var db = new InMemoryDatabaseFixture<AppDbContext>(opts => new AppDbContext(opts));
The contextFactory delegate means any constructor shape works. It pairs with
GM.EntityFramework.Persistence conventions without taking a versioned dependency on that package —
same decoupling philosophy as the rest of the ecosystem.
Test data builders (in core)
TestDataBuilder<T> is a dependency-free fluent base for object-mothers — works for mutable classes
(.With(x => …)) and immutable records (.Customize(x => x with { … })), with .Build(),
.BuildMany(n), and an implicit conversion to T.
Runnable usage lives in GM.Testing.Samples.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- GM.Messaging.Domain (>= 1.2.0)
- GM.Testing (>= 1.0.0)
- WolverineFx (>= 6.24.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 79 | 8/7/2026 |