Mocking third-party dependencies

stubsmockingblack-box testing
Contents
Per language HTTPgRPCWebSocket

Your service calls things you don’t run: a payment provider, another team’s gRPC service, a price feed. In a test you can’t make them fail on purpose, and you can’t ask them what your service sent. So tomato starts a stub in their place.

How it works

tomato starts a stub in place of the third party, points the service at it, tells the stub what to answer, and checks what the service sent it.tomato runGiven … stub …When … sends …Then … received …features/*.featureshopyour servicesame code, either wayPAYMENTS_URL=https://api.stripe.comStripenot yoursanswersPOST /v1/charges → 201receivedPOST /v1/chargesHTTPHTTPthe scenario sets the answer, then reads what arrived

In production the service calls a dependency you don't run: Stripe. You can't make it fail on demand, and you can't ask it what your service sent.

Point. tomato starts a http-server stub and hands the service its address in the same variable. The service's code doesn't change.

Arrange. A Given step tells the stub what to answer: a success, an error, a delay, a refused connection.

Act. The scenario calls your service; your service calls the stub, which answers as told and remembers the call.

Assert. Then steps check your service's answer and what it sent the stub: the path, headers, body, metadata, messages.

Reset. Before the next scenario the stub forgets its answers and what it received. Scenarios never see each other's stubs.

Four moves, the same for every protocol:

  1. Point. The stub listens on a port you choose. The service gets that address in the same variable that holds the real one in production.
  2. Arrange. A Given step tells the stub what to answer.
  3. Assert. Then steps check what your service sent the stub.
  4. Reset. Before every scenario the stub forgets everything.

Which stub

The service callsStubPoint it withArrangeAssert
a REST APIhttp-server{{.name.url}}stub "POST" "/path" returns "201"received "POST" "/path"
a gRPC servicegrpc-server{{.name.address}}stub "pkg.Svc/Method" returns json:received "pkg.Svc/Method"
a WebSocket feedwebsocket-serverws://localhost:<port>on message "…" replies:received message "…"

Name a stub after what it stands in for. "stripe-stub" received … reads on its own, and never gets confused with the client that calls your service, "shop-client".

Stub, not the real thing

A stub checks your side of the contract: what your service sends, and what it does with each answer. It can’t tell you the third party still answers that way. Keep a few smoke tests against its sandbox for that, and let the stub cover the cases a sandbox won’t give you on demand: the decline, the timeout, the outage.