Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
History note: All version sections were reformatted on 2026-07-05 for one-time CONVENTIONS §CHANGELOG conformance: forbidden sub-headers folded into the six Keep a Changelog headers, non-user-facing content removed (internal refactors, test counts, coverage numbers, CI and build hygiene, governance churn, roadmap notes), bullets kept in past-tense active voice with code-formatted API leads. The nuget.org Release Notes tab and the GitHub Release for each shipped version are unchanged. A CI
family-lintgate keeps future sections conforming; each is frozen per Rule 7 once shipped.
Unreleased
0.3.0 - 2026-06-14: server-streaming builders and assertions
Minor release. Extends the builder and the assertion surface from unary calls to server-streaming calls: GrpcCallBuilder now constructs AsyncServerStreamingCall<T> test doubles, and a Streams() chain asserts on the streamed responses. Purely additive; the 0.2.0 ApiCompat baseline is preserved.
Added
GrpcCallBuilder.ServerStreaming<TResponse>(IEnumerable<TResponse> responses)builds a successful server-streaming call whose response stream yields the responses in order and then ends cleanly with a terminalStatusCode.OK.GrpcCallBuilder.ServerStreamingFaulted<TResponse>(StatusCode statusCode, IEnumerable<TResponse> responses, string? detail = null)builds a server-streaming call that yields the responses and then throws anRpcExceptionon the next read, so a wrapper's mid-stream fault handling can be exercised.Streams()on anAsyncServerStreamingCall<TResponse>begins a server-streaming assertion that reads the response stream once. ChainStreamsAtLeast(int)/StreamsExactly(int)to assert the response count,StreamContains(Func<TResponse, bool>)to assert at least one response matches (the predicate text is captured for the failure message), andAndStreamItems(Func<IReadOnlyList<TResponse>, Task>)to run follow-on assertions against the materialized responses without re-reading the stream. A stream that faults mid-read fails with the partial count and the rendered gRPC outcome.
0.2.0 - 2026-06-12: response and trailer metadata on the builder, trailer assertions
Minor release. Lets a faked call carry response headers and trailers, and adds trailer assertions (text and binary) to the RpcException chain. Purely additive.
Added
GrpcCallBuilder.Success<T>(T response, Metadata? responseHeaders, Metadata? trailers)andGrpcCallBuilder.Faulted<T>(StatusCode statusCode, string? detail, Metadata trailers)build calls that carry response metadata, so a wrapper that reads response headers or trailers can be tested against a fake. A null headers or trailers argument is treated as empty metadata.WithTrailer(string key, string value)andWithTrailer(string key, ReadOnlySpan<byte> value)on theThrowsGrpcException()chain assert the exception'sTrailerscontain a text or binary (-bin) entry. Keys are matched case-insensitively (gRPC lowercases metadata keys). The text overload readsMetadata.Entry.Valueand the binary overload readsMetadata.Entry.ValueBytes, each guarded byEntry.IsBinary, so the value of a binary entry is never read as a string. On failure the message renders the trailers, with binary entries shown as(binary, N bytes).
Changed
- README adds an "Await the call against a generated client" note: a generated client's
XAsyncreturnsAsyncUnaryCall<T>whose fault lives inResponseAsync, so a delegate that returns the call without awaiting it never surfaces the fault and the assertion reports no exception. Assert viaasync () => await client.XAsync(...)orclient.XAsync(...).ResponseAsyncwhen testing a raw generated client; wrapper tests, which returnTask, are unaffected.
Fixed
GrpcCallBuilder.Success<T>now returns the same trailersMetadatainstance on every call to the trailers accessor, matching a real client; it previously returned a fresh emptyMetadataeach time, so an identity check or mutation did not behave as it would against a real call.
0.1.2 - 2026-06-05: documentation refresh
Documentation and release-tooling release. No API or behavior change.
0.1.1 - 2026-06-04: README cookbook recipes
Documentation patch. No public-surface change; the shipped assemblies are identical to v0.1.0.
Changed
- README adds a "Replacing hand-rolled
AsyncUnaryCall<T>factories" recipe as the lead Cookbook entry: a before/after that contrasts the five-parameterAsyncUnaryCall<T>constructor (response task, response-headers task, status accessor, trailers accessor, dispose callback) withGrpcCallBuilder.Success(response)andGrpcCallBuilder.Faulted<T>(...), and spells out the type-inference rule (Success<T>(T)infersT;Faulted<T>(RpcException)needs the explicitT). This is the highest-value use of the builder: deleting fake-client constructor boilerplate. - README adds a "When not to use
ThrowsGrpcException" recipe: tests that assert the wrapper rethrows the sameRpcExceptioninstance are a stronger contract thanThrowsGrpcException(code)and stay onThrows<RpcException>()plusIsSameReferenceAs, since matching only the status code would weaken an identity-propagation test. - The two packed package READMEs link to the new Cookbook recipes and restate the
Success<T>/Faulted<T>inference rule, kept consistent with the GitHub README.
0.1.0 - 2026-06-02: gRPC outcome assertions
Feature release. Lifts the package from skeleton to functional: the gRPC outcome-assertion surface ships. ThrowsGrpcException() asserts a delegate threw an RpcException, with StatusCode shorthands and Status.Detail refinements; DoesNotThrowGrpcException() covers the benign-error swallow tests; and GrpcCallBuilder removes the five-parameter AsyncUnaryCall<T> constructor that every hand-rolled gRPC fake repeats. The v0.0.1 IsRpcException() discriminator is preserved unchanged.
Added
Assert.That(() => call).ThrowsGrpcException()asserts the delegate throws a gRPCRpcExceptionof any status;ThrowsGrpcException(StatusCode expected)asserts the status. Both return aGrpcExceptionAssertionchain, and failure messages render the actualStatusCodeandStatus.Detail. The entry points extend the delegate assertion source produced byAssert.That(() => client.Method(...)).- 14
StatusCodeshorthands chaining offThrowsGrpcException():IsOk,IsCancelled,IsInvalidArgument,IsDeadlineExceeded,IsNotFound,IsAlreadyExists,IsPermissionDenied,IsResourceExhausted,IsFailedPrecondition,IsAborted,IsUnimplemented,IsInternal,IsUnavailable,IsUnauthenticated. WithDetail(string)(exact, ordinal) andWithDetailContaining(string, StringComparison)(substring, explicit comparison per the family convention) refine the assertion onStatus.Detail.Assert.That(() => call).DoesNotThrowGrpcException()asserts the delegate completes without throwing anRpcException. A thrownRpcExceptionfails with the gRPC outcome rendered; any other thrown exception fails naming its type and message.GrpcCallBuilder(coreGrpcAssertionspackage):Success<T>(T),Faulted<T>(RpcException), andFaulted<T>(StatusCode, string?)buildAsyncUnaryCall<T>instances for gRPC client test doubles, replacing the repeated five-parameter constructor. Both builders guard their arguments withArgumentNullException.ThrowIfNull, a strict improvement over the hand-rolled fakes that would dereference-null later.GrpcOutcomeRendering(core):Describe(RpcException)andDescribeStatus(StatusCode, string?)render the status and detail (truncated atMaxDetailLength, 200 characters, with a horizontal-ellipsis suffix) for failure messages, shared so consumer-authored gRPC assertions produce identical diagnostics.
0.0.1 - 2026-06-01: skeleton release
Skeleton release. Establishes the repository, the GrpcAssertions (core) and GrpcAssertions.TUnit (adapter) package identifiers on nuget.org, and the full family quality bar (AOT-clean with no runtime reflection in the assertion path, SBOM plus SLSA build provenance, public-API snapshot pinning). Ships a single discriminator, IsRpcException(), so the surface is real and exercised end-to-end rather than empty. The gRPC outcome-assertion surface (the GrpcCallBuilder test infrastructure plus the ThrowsGrpcException verbs and StatusCode shorthands) lands in v0.1.0.
Added
Assert.That(exception).IsRpcException()asserts that anExceptionis a gRPCRpcException, the exception type a failed gRPC call surfaces. The failure message names the actual exception type. Source-generated via[GenerateAssertion]; the generated chain is AOT-clean.GrpcExceptions.IsRpcException(Exception?)framework-agnostic predicate in theGrpcAssertionscore package. Returnsfalsefornulland for any non-RpcExceptiontype. The adapter assertion is a thin wrapper over this helper, so a future xUnit, NUnit, or MSTest adapter reuses the same logic.- Single disclosed runtime dependency:
Grpc.Core.Api(Apache-2.0), the package that defines theRpcException/StatusCode/Statustypes the assertions are about. It flows transitively to consumers through the core package.