Use when gRPC client code needs tests against a controllable fake server, or a gRPC interceptor needs tests: Go bufconn and mockgen, Python pytest-grpc, JVM in-process servers, Node fake servers, with success, error and timeout responses. Streaming sequences are grpc-streaming-test-author.
80%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
gRPC interceptors apply cross-cutting behavior (auth, retry, logging, error mapping) to every RPC without touching service logic. They are one of the primary sources of subtle gRPC bugs: silent metadata drops, wrong ordering in a chain, and retry storms that ignore backoff. This skill produces isolated unit tests for each interceptor behavior.
Per the gRPC interceptors guide, interceptors are "per-call" and are split into client-side and server-side variants, each further divided into unary and streaming forms.
Scope: the host SKILL.md authors tests for service handler logic using
an in-process server; this reference tests the interceptor layer itself,
not the handler. grpc-streaming-test-author covers multi-message stream
sequences; this reference covers interceptors that wrap streams (e.g., a
server stream interceptor that injects a header before the first message).
| Variant | Go type (pkg.go.dev/google.golang.org/grpc) | Java type (grpc-java javadoc) | grpc-js |
|---|---|---|---|
| Server unary | grpc.UnaryServerInterceptor | ServerInterceptor.interceptCall | N/A (server-only via grpc package) |
| Server streaming | grpc.StreamServerInterceptor | ServerInterceptor.interceptCall | N/A |
| Client unary | grpc.UnaryClientInterceptor | ClientInterceptor.interceptCall | InterceptorProvider option |
| Client streaming | grpc.StreamClientInterceptor | ClientInterceptor.interceptCall | InterceptorProvider option |
Full type signatures are at the reference links in each language playbook.
The canonical test pattern for all languages is:
handler
(the next leg in the chain).This avoids spinning up a full in-process server just to test cross-cutting logic. Use the host SKILL.md's in-process server only when testing the interaction between an interceptor and a handler.
Each playbook holds the full type signatures, test patterns, and runnable examples for one language surface:
ServerInterceptor auth rejection, ClientInterceptor outbound token
injection, intercept() vs interceptForward() ordering:
java-interceptors.md.InterceptingCall:
grpc-js-interceptors.md.handler (the next leg) that records what it received and
whether it was called at all.-race, -count=1) and wire into CI.Scenario: a Go client retry interceptor must retry transient failures but never retry an auth failure.
retryInterceptor(maxRetries(3), noSleep()), a
grpc.UnaryClientInterceptor.invoker that returns codes.Unavailable on the first two calls
and nil on the third, incrementing callCount each time.context.Background() and the spy; assert
err == nil and callCount == 3 - it retried twice, then succeeded.invoker that always returns
codes.PermissionDenied; assert st.Code() == codes.PermissionDenied and
callCount == 1 - a non-transient code is surfaced immediately, not retried.go test ./... -run TestRetry -race -count=1. Result: two passing tests
proving backoff fires on Unavailable and is skipped on PermissionDenied, with
no auth-storm regression.Full code for both tests is in go-interceptors.md.
These tests run as ordinary unit tests in each language:
go test ./... -run TestAuth -race # Go: -race catches metadata races
mvn test -Dtest=AuthInterceptorTest # Java / Maven
npx jest --testPathPattern=interceptor # Node / JestUse -race in Go: concurrent ctx + metadata access in interceptors
surfaces races that pass without the flag.
jobs:
interceptor-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with: { go-version: stable }
- run: go test ./... -race -count=1 -timeout=30s-count=1 disables the test cache so metadata-mutation tests are not
silently skipped on re-runs.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Asserting on error message strings | Text is not part of the gRPC contract; changes with i18n | Assert on status.Code() only |
| Testing the interceptor only via an end-to-end call | Chain bugs and ordering issues are invisible when everything succeeds | Call the interceptor function directly with a spy handler |
Assuming intercept() and interceptForward() are identical | Java ServerInterceptors.intercept() applies interceptors in reverse order per the javadoc | Use interceptForward() when declaration order must match execution order |
time.Sleep inside retry-interceptor tests | Slow tests; sleep duration is arbitrary | Inject a fake sleep function via an option or dependency parameter |
| Sharing a single metadata map across test cases | Map mutation bleeds between cases | Construct a fresh metadata.MD / Metadata per test |
| Not testing the "does not retry" case for non-transient codes | Retry interceptors that retry PermissionDenied cause auth-storm bugs | Add explicit tests for codes.PermissionDenied and codes.InvalidArgument |
| Embedding real tokens in test metadata | Secrets in source history | Use constant placeholder strings like "Bearer test-token-value" |
ServerStream / ClientStream
implementations. Minimal fakes satisfy most tests; complex
multi-message sequences belong in
grpc-streaming-test-author.@grpc/grpc-js
server does not expose a ServerInterceptor extension point in the
same way the Java or Go servers do.grpc.ChainUnaryInterceptor ordering only applies to the server.
Client chaining uses grpc.WithChainUnaryInterceptor; the two have
the same semantics but different registration functions per
pkg.go.dev/google.golang.org/grpc.UnaryServerInterceptor, StreamServerInterceptor,
UnaryClientInterceptor, StreamClientInterceptor, chain functions):
pkg.go.dev/google.golang.org/grpc.FromIncomingContext, AppendToOutgoingContext):
pkg.go.dev/google.golang.org/grpc/metadata.Unauthenticated=16, PermissionDenied=7, Unavailable=14):
pkg.go.dev/google.golang.org/grpc/codes.ServerInterceptor.interceptCall signature:
grpc.github.io/grpc-java/javadoc/io/grpc/ServerInterceptor.html.ClientInterceptor.interceptCall signature:
grpc.github.io/grpc-java/javadoc/io/grpc/ClientInterceptor.html.ServerInterceptors.intercept vs interceptForward ordering:
grpc.github.io/grpc-java/javadoc/io/grpc/ServerInterceptors.html.grpc-streaming-test-author (references/status-codes.md).grpc-streaming-test-author.