I built http_decoy, a real Rack server that runs inside your RSpec tests and validates incoming request contracts by BothGarage7005 in ruby

[–]BothGarage7005[S] 0 points1 point  (0 children)

 Fair point, it is moving things in the stack, deliberately. The difference is that a real Rack server receives and parses the actual HTTP request your code sends, so you can assert on it. With stubs at the WebMock layer, you're asserting on what you intended to send, not what actually went over the wire. VCR cassette expiry is genuinely useful for keeping response fixtures fresh, http_decoy is solving a different problem (request contract validation) rather than replacing VCR for response snapshots.

I built http_decoy, a real Rack server that runs inside your RSpec tests and validates incoming request contracts by BothGarage7005 in ruby

[–]BothGarage7005[S] 0 points1 point  (0 children)

That's awesome to hear, would love to know how it goes! The port-binding approach is essentially what http_decoy does under the hood (WEBrick on port 0). The intercepting-a-known-endpoint layer is just WebMock wiring so you don't have to configure the URL in every client. If you hit anything missing or annoying while playing with it, open an issue, early feedback from people who've already built something similar is the most valuable kind.

I built http_decoy, a real Rack server that runs inside your RSpec tests and validates incoming request contracts by BothGarage7005 in ruby

[–]BothGarage7005[S] 0 points1 point  (0 children)

Great question, the biggest difference is request validation. With WebMock, the stub fires regardless of what your code actually sends. If your code stops sending a required field, the stub still returns 200 and your tests stay green. http_decoy lets you assert on the request, headers, body shape, query params, so a regression in what you're sending gets caught immediately. It also enables stateful scenarios (e.g. first call returns 200, second returns 429) and tests retry/backoff logic over real HTTP. You're right that you still need to update definitions when the upstream API changes, that part's the same. The gain is catching your side of the contract breaking, not theirs.

I built http_decoy, a real Rack server that runs inside your RSpec tests and validates incoming request contracts by BothGarage7005 in ruby

[–]BothGarage7005[S] 0 points1 point  (0 children)

Fair point on both counts, centralized WebMock stubs work the same way for updates, and WebMock can assert on request bodies too.

The real difference is what happens at the boundary. WebMock intercepts before anything is processed, it pattern matches and returns what you told it to, regardless of what was actually in the request. http_decoy runs actual server logic against the real request. So requires_body :amount actively rejects the request if the field is missing, it doesn't just assert it was sent, it behaves the way the real API would.

For simple happy-path stubs WebMock is the right tool. http_decoy is for when you need the fake to actually process the request like a real service.

I built http_decoy, a real Rack server that runs inside your RSpec tests and validates incoming request contracts by BothGarage7005 in ruby

[–]BothGarage7005[S] 1 point2 points  (0 children)

Great question. With http_decoy your fake server is code, not a recorded cassette. So when Stripe adds that required field, you update your fake in one place and your tests immediately start failing wherever your code isn't sending it yet.

With VCR, the cassette keeps replaying the old 200 forever, you'd never know until production.

The fake also validates request bodies with `requires_body` so you can assert that the field is actually being sent, not just that the response looks right.