Rig.TUnit.Messaging.Nats
0.1.0-beta.2
dotnet add package Rig.TUnit.Messaging.Nats --version 0.1.0-beta.2
NuGet\Install-Package Rig.TUnit.Messaging.Nats -Version 0.1.0-beta.2
<PackageReference Include="Rig.TUnit.Messaging.Nats" Version="0.1.0-beta.2" />
<PackageVersion Include="Rig.TUnit.Messaging.Nats" Version="0.1.0-beta.2" />
<PackageReference Include="Rig.TUnit.Messaging.Nats" />
paket add Rig.TUnit.Messaging.Nats --version 0.1.0-beta.2
#r "nuget: Rig.TUnit.Messaging.Nats, 0.1.0-beta.2"
#:package Rig.TUnit.Messaging.Nats@0.1.0-beta.2
#addin nuget:?package=Rig.TUnit.Messaging.Nats&version=0.1.0-beta.2&prerelease
#tool nuget:?package=Rig.TUnit.Messaging.Nats&version=0.1.0-beta.2&prerelease
Rig.TUnit.Messaging.Nats
Two parallel fixtures: core NATS (
NatsFixture) for low-latency pub/sub and JetStream (NatsJetStreamFixture) for durable streams + ordered consumers, with a fluent topology builder.
What this package is
The Rig.TUnit NATS provider. Two fixture flavours, both Testcontainers-backed:
NatsFixture—nats:2.10-alpineserver, JetStream off; shipsNatsListener+NatsEventSenderfor core pub/sub testing.NatsJetStreamFixture— same image with-jsflag, exposes anINatsJSContext; ships:NatsJetStreamEventSender— publishes viaINatsJSContext.PublishAsyncwith aSendContextoverload that writesx-correlation-id+x-session-keyheaders.NatsJetStreamListener— ordered consumer (CreateOrderedConsumerAsync); supports multi-subject filters and survives reconnects without duplicates. Readsx-session-keyintoCapturedMessage.SessionKey.NatsTopologyBuilder+ provider-scoped interfaces (INatsTopologyBuilder,INatsStreamConfig,INatsConsumerConfig,NatsRetentionPolicyenum) wired viaNatsRigBuilder.WithTopology(…). Stream creation is idempotent viaJSStreamNameExistErr(10058) →UpdateStreamAsyncfallback.
The core-NATS API is unchanged from prior releases. JetStream is a
strictly additive layer (Feature 007 Phase 5) and uses an isolated
NATS.Client.JetStream package reference, guarded by an architecture
test (DependencyDirectionTests).
When to use it
- Core NATS — low-latency pub/sub scenarios (sub-millisecond even through Docker networking), best-effort delivery is acceptable.
- JetStream — durable streams, ordered consumers, retention policies, multi-subject filtering, ack-tracked delivery.
- Asserting per-key ordering on a JetStream subject hierarchy.
- Not for: pure unit tests of message-handler logic.
Prerequisites
- .NET 10 SDK
- Docker Desktop / Colima (NATS image ~20 MB)
NATS.Client.Core(transitive)
Quick start
Core NATS — best-effort pub/sub:
using Rig.TUnit.Messaging.Nats.Fixtures;
using Rig.TUnit.Messaging.Nats.Helpers;
await using var fx = new NatsFixture();
await fx.InitializeAsync();
await using var sender = new NatsEventSender(fx.ConnectionString, subject: "orders");
await sender.SendAsync("{\"orderId\":1}", correlationId: "abc");
JetStream — durable + ordered:
using Rig.TUnit.Messaging.Helpers;
using Rig.TUnit.Messaging.Nats.Fixtures;
using Rig.TUnit.Messaging.Nats.Helpers;
var fx = await SharedNatsJetStreamFixture.GetAsync();
// 1) Declare topology at runtime.
services.AddRigTUnit(rig =>
rig.UseNats(RigConnect.FromValue(fx.ConnectionString), n =>
n.WithTopology(t => t.Stream("ORDERS", c => c
.WithSubjects("orders.>")
.WithMaxMessages(10_000)
.WithRetentionPolicy(NatsRetentionPolicy.Limits)))));
// 2) Send + ordered receive.
await using var sender = new NatsJetStreamEventSender(fx.JetStream, "orders.eu");
await using var listener = new NatsJetStreamListener(fx.JetStream, "ORDERS", "orders.eu");
await listener.StartAsync(ct);
await sender.SendAsync(
"payload-1",
context: new SendContext(SessionKey: "session-abc"),
ct: ct);
Options
| Property | Type | Default | Description |
|---|---|---|---|
Image |
string |
"nats:2.10-alpine" |
NATS image |
StartupTimeoutSeconds |
int |
30 |
NATS boots in ~1 s |
EnableJetStream |
bool |
false |
Durable streams |
JetStreamStorageDir |
string? |
null |
JetStream persistence path |
Fixture + helper APIs
Core NATS:
Rig.TUnit.Messaging.Nats.Fixtures.NatsFixtureRig.TUnit.Messaging.Nats.Options.NatsFixtureOptionsRig.TUnit.Messaging.Nats.Builder.NatsRigBuilder— shipsWithTopology(Action<INatsTopologyBuilder>).Rig.TUnit.Messaging.Nats.Helpers.NatsListenerRig.TUnit.Messaging.Nats.Helpers.NatsEventSender
JetStream (Feature 007 Phase 5):
Rig.TUnit.Messaging.Nats.Fixtures.NatsJetStreamFixture·SharedNatsJetStreamFixtureRig.TUnit.Messaging.Nats.Helpers.NatsJetStreamEventSender— shipsSendAsync(string, SendContext, …)overload.Rig.TUnit.Messaging.Nats.Helpers.NatsJetStreamListener— ordered consumer with optional multi-subject filters.Rig.TUnit.Messaging.Nats.Topology.INatsTopologyBuilderRig.TUnit.Messaging.Nats.Topology.INatsStreamConfig—WithSubjects,WithMaxMessages,WithRetentionPolicy.Rig.TUnit.Messaging.Nats.Topology.INatsConsumerConfigRig.TUnit.Messaging.Nats.Topology.NatsRetentionPolicy— enum (Limits,Interest,WorkQueue).Rig.TUnit.Messaging.Nats.Topology.NatsTopologyBuilder
Per-test isolation
Per-test subject: orders.{IsolationKey:short}. NATS subjects are
namespace-scoped by dot-separated tokens so parallel tests never
collide. No teardown needed — subjects are ephemeral.
Parallelism + performance
- First-run pull: ~2 s.
- Warm startup: ~1 s.
- Per-op send + receive: ~500 µs.
- Parallelism: essentially unbounded — NATS is designed for it.
Troubleshooting
No responderserror — the subscriber wasn't active when the publisher fired. Uselistener.StartAsync+MessageAssert.Withinrather than a fixed sleep.- JetStream stream missing —
EnableJetStream=falseby default; turn it on explicitly.
See docs/troubleshooting.md#nats.
Provider quirks + edge cases
- NATS core is best-effort delivery — a restart loses unacked messages.
Durability needs JetStream; use
NatsJetStreamFixture(separate fixture, separate package reference, separate CI matrix entry). - Subject wildcards (
orders.*,orders.>) match differently —*is single-token,>is multi-token. Tests asserting on wildcard routing must be explicit. - JetStream stream creation is idempotent: the topology builder
catches
NatsJSApiException.Error.ErrCode == 10058(JSStreamNameExistErr) and falls back toUpdateStreamAsync. Other 400-coded errors (e.g. invalid retention policy) propagate so they surface in tests. NatsJetStreamListener.StartAsynccreates the ordered consumer synchronously before spawning the consume loop — so a fast publisher can never race a not-yet-existing consumer, and any broker error (auth, missing stream) propagates to the test.SendContext.PartitionKeyis unused on JetStream — partitioning is done via the subject hierarchy. UseSessionKeyfor per-key ordering, mapped to thex-session-keyheader.- The JetStream code path sits behind its own
NATS.Client.JetStreampackage reference, whichDependencyDirectionTestsenforces is referenced only byRig.TUnit.Messaging.Nats.
Benchmarks
See NatsMessagingBenchmarks.cs;
baseline in benchmarks/baseline-005.json.
Related docs
- Architecture diagram
- Glossary
- Provider deep-dive:
docs/providers/nats.md(JetStream stream / consumer examples, retention policies, dependency guard). - Family base:
Rig.TUnit.Messaging - Feature design: Sessions & Partitions · Topology Builder
License
MIT. See LICENSE.
| 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
- Bogus (>= 35.6.1)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
- NATS.Client.Core (>= 2.5.0)
- NATS.Client.JetStream (>= 2.5.0)
- Rig.TUnit.Messaging (>= 0.1.0-beta.2)
- Testcontainers (>= 4.11.0)
- Testcontainers.Nats (>= 4.11.0)
- TUnit.Core (>= 1.34.5)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Rig.TUnit.Messaging.Nats:
| Package | Downloads |
|---|---|
|
Rig.TUnit.All
Meta-package containing every Rig.TUnit.* package. DISCOURAGED — prefer per-feature or per-stack meta-packages (Rig.TUnit, Rig.TUnit.Microservices). |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-beta.2 | 52 | 4/27/2026 |
| 0.0.0-alpha.0.14 | 52 | 4/26/2026 |