Skip to content
Development

Writing tests that check the right thing

By Victor Da Luz
railsrubytestingdev-logblog-manager

Closed out a small backlog item today: two files with real coverage gaps from an earlier audit. One job had never been tested at all, and one HTTP client had zero tests. Not exciting work on its face, but it turned into a good reminder about the difference between a test that passes and a test that actually verifies something.

What I built

For the GitHub client, I needed a way to test HTTP calls without hitting the real API. Rather than invent something new, I went looking at how the codebase already solves this - and found it, in a sibling client for a different service. It takes an optional object that can stand in for the real HTTP connection, defaulting to the real thing in production. I copied the exact shape: same parameter name, same fallback logic, same comment. When a pattern already exists and works, matching it is worth more than a clever alternative.

For the job, I followed the pattern already used by every other job test in the suite: swap out the class it depends on for a fake, temporarily, then put the real one back when the test finishes. Straightforward, and it matched four other files doing the same thing.

What surprised me

Both sets of tests passed cleanly on the first run. That should have been reassuring. Instead, when I ran a review pass on the diff, it asked a more useful question than “does this pass” - it asked “what would this test actually catch.”

Turned out: not much, in a couple of spots. My job test’s fake didn’t care what arguments it was called with - just that it was called. If a future change silently dropped one of the four things the job hands off (say, forgot to pass along the scheduling time), every test would keep passing, because nothing was checking. Same story on the HTTP client side: the fake connection ignored the actual request being built and always handed back a canned response. A bug that mangled the URL path, or dropped the authorization header, would sail through undetected.

Both were quick to fix once named. I made the fakes capture what they were called with, then added assertions against that. For the job: does it hand the right post and the right arguments to the thing that actually talks to Postiz? For the client: does it build a request to the right path with the right header? Small changes, but they’re the difference between “this doesn’t crash” and “this does what it’s supposed to.”

The review also caught something pettier and non-negotiable: I’d copied a comment from that sibling client verbatim, em dash included. Global style rule says no em dashes, anywhere, including code comments. Easy fix, but a good reminder that copying a pattern means copying its flaws too unless you’re paying attention.

Last thing: two lines of setup that looked necessary weren’t. One configured an API key that the test’s mocking made irrelevant - the real code path that reads it never runs when you’ve replaced the whole object it belongs to. I didn’t just delete it on a hunch; I removed it, reran the tests, watched them still pass, and only then trusted it was safe to leave out.

What’s next

Nothing pending on this one. The recurring lesson across today’s work: a green test suite tells you the code you wrote didn’t crash under the exact conditions you wrote it for. Whether it would actually catch a real regression is a separate question, and it’s worth asking explicitly instead of assuming the answer is yes.

Related reading