GM.Testing 1.0.0

dotnet add package GM.Testing --version 1.0.0
                    
NuGet\Install-Package GM.Testing -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GM.Testing" Version="1.0.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GM.Testing" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="GM.Testing">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GM.Testing --version 1.0.0
                    
#r "nuget: GM.Testing, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GM.Testing@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GM.Testing&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=GM.Testing&version=1.0.0
                    
Install as a Cake Tool

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-flight GetOrCreateAsync, honours absolute/sliding TTL against an injectable TimeProvider (fast-forward a test clock to prove expiry). Inspect Count / Keys / TryPeek.
  • FakeDistributedLock (IDistributedLock) — real in-process mutual exclusion so lock-guarded code can be tested under contention, or AlwaysAcquire = true for a no-op lock. Inspect HeldResources.
  • FakeFileStorageService (IFileStorageService) — byte-array backed; honours ExpectedChecksum (SHA-256 → ChecksumMismatchException) and not-found semantics. Inspect Keys / 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on GM.Testing:

Package Downloads
GM.Testing.AspNetCore

WebApplicationFactory helpers for integration-testing GM.API-based services. GmWebApplicationFactory<TProgram> is a reusable test-host base with a fluent surface for the things every integration test needs: in-memory configuration overrides (.WithConfig), test-specific DI overrides that win over the app's registrations (.WithServices / .ReplaceService — e.g. swap a real cache for a GM.Testing.Fakes double), and a test environment name. Pulls in GM.Testing for the HTTP assertion helpers. Test-only (DevelopmentDependency).

GM.Testing.Messaging

Published-event capture for testing GM.Messaging consumers — assert "this integration event was published" without a real RabbitMQ broker. CapturingMessageBus stands in for Wolverine's IMessageBus and records every published/sent/scheduled message; PublishedMessages / Published<TEvent>() / ShouldHavePublished<TEvent>() make the assertions terse. Register with AddCapturingMessageBus() in a GmWebApplicationFactory override, or use the recorder directly in a unit test. Test-only (DevelopmentDependency).

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 109 8/7/2026