Back to Ideas 5 min read

The Words We Work In

E
EkoHacks Team
·
The Words We Work In

Open any review here for long enough and the same handful of words turn up. Nullable, embedded stub, stub object, output tracking. They are not ours by birth. They come from James Shore's pattern language for testing without mocks, and we lean on it because the problems in EkoLite keep arriving in exactly its shapes. What we have done is ground each word in our own code, so that when one of them lands in a review it points at a real line and not at an idea.

A shared word does a quiet, large thing. It turns a review from a difference of taste into a conversation about the code. So here is the short dictionary, grouped the way the ideas actually sit, with the place each one lives in EkoLite.

The wrapper and its switch

Infrastructure wrapper. The one class that owns a single piece of the outside world. WebSocketWrapper owns the socket layer, the Mongo wrapper owns the database, the client socket owns the browser's connection. Nothing else in the app talks to those systems directly. The wrapper gives the rest of the code a clean view and keeps the mess in one place.

Nullable. An infrastructure wrapper with an off switch. It is built one of two ways, create() for the real thing and createNull() for the version that behaves the same but does not reach the world. It is not a test double. It is production code that can be switched off, which is why a nulled wrapper is also useful for warming a cache or running a dry run, not only in tests. WebSocketWrapper.createNull() is the one we have been living in.

The three fakes, which is where everyone trips

These three sound alike and are constantly mixed up. The distinctions are most of the value.

Embedded stub. The in file stand in for the third party code beneath your wrapper. It has no off switch of its own. It is the off itself. When you call createNull(), this is the thing wired in underneath, so the wrapper's real logic runs against something that does not touch the network. In EkoLite the embedded stub for the socket layer is the in memory stand in for the ws library: the null connection source and the null socket. They are dumb and permanently off. The wrapper above them is the clever part.

Stub object. A separate fake that stands in for your wrapper's own behaviour, rather than for the third party beneath it. The tell is that your production code has to special case it, an if choosing a real path or a test path. It passes its tests, and it quietly splits the real path from the tested path, so a test can stay green while the real thing is broken. It is the one to avoid, and the one we built by accident the day send started asking if (entry.stub).

How to tell them apart. Two questions settle it. Does it have a createNull()? Then it is the Nullable, the whole wrapper. Is it the dumb permanently off fake that createNull() injects? Then it is the embedded stub. And if your wrapper can tell, while it runs, whether it is holding the real thing or the fake, you have neither. You have a stub object.

How the tests see the wrapper

Sociable test. A unit test that lets the code under test run its real dependencies instead of swapping them out. It stays pointed at the one thing it is testing, but the dependencies underneath are the real ones.

Overlapping sociable test. The reason it is worth the bother. Because the test runs the real dependency's real code, breaking that code breaks the test. The tests overlap like links in a chain, and the chain gives you the reach of broad tests without their cost. This is the property our disconnect test had lost, and the property we work to keep.

State based test. A test that checks the output or the state the code produced, not which methods got called on the way. You assert that the client received this message, not that send was invoked. It reads as arrange, act, assert, and it lets you change how the code works inside without rewriting the test.

What the wrapper offers a test

Output tracking. A way to see what the wrapper actually did, the writes it would have sent to the world, recorded as the thing the caller cares about rather than the raw call. trackConnections, trackDisconnections and trackMessages are ours. The rule that matters: tracking works the same on the real wrapper and the null one, because it is one wrapper. Tracking that only works on the null is the old stub object showing through.

Behavior simulation. Methods that stand in for an event the outside world would have pushed at you, a client connecting, a message arriving, a socket closing. simulateConnection is ours. The discipline is that the simulated event and the real event run the same handler, so the thing you tested is the thing that ships.

Configurable responses. When a nulled wrapper has to answer with something, you pass the answer you want into createNull(), described in terms the caller understands rather than the raw shape of the wire. It keeps the test setup small and honest.

Where the real proof lives

Narrow integration test. The small, slow test that proves the wrapper genuinely talks to the real system, kept tight to that one boundary. Our ws and Fastify suites are these. Once a wrapper is properly Nullable, these shrink to one job, proving a real connection arrives and a real close fires, and everything about what the wrapper then does moves into the fast null tests.

The shortcut for the code on top

Fake it once you make it. Most code is not a low level wrapper sitting on a third party library. It sits on other wrappers. To make that code Nullable you do not write a fresh stub, you build it from dependencies that are already nullable and pass the configuration down. You only hand write an embedded stub at the very bottom, where your code finally meets someone else's.


A glossary looks like the dry end of the work, the bit you write once and never read. We have found the opposite. The day the team shares these words is the day a review stops being one person's opinion against another's and becomes two people pointing at the same line, with the same name for what is wrong. The need taught us the words. Writing them down is just making sure the next person does not have to hit the same wall to learn them.

E

Written by

EkoHacks Team

More from Ideas

·6 min read

What the Nullable Gave Back

One file, seven behaviours held fixed, the database swapped for a Nullable: about 180 times less time inside the tests, and coverage flat to two decimals.

E
EkoHacks Team
·6 min read

Twenty Six More Tests, Four Fewer Behaviours

Removing the mocks grew the suite from 44 tests to 70 and quietly deleted four behaviours, every one of them a failure path. Test count is not coverage.

E
EkoHacks Team
·6 min read

The Best Coverage Number in the Room

Same commit, same spec, same test count. The mocked suite ran 5.6 times faster, covered 3.5 fewer points of real code, and posted the best branch coverage.

E
EkoHacks Team