Skip to content
Development

Proving a fallback actually falls back

By Victor Da Luz
astrotestingimagesdev-logastro-tools

My OG-card generator had one test. It rendered a fixture card and checked the output was a non-empty PNG. That’s a fine smoke test, and it caught a real crash once - a native panic in the PNG library I was using before switching to sharp. But it left the package’s actual configurable surface, custom dimensions and custom fonts, completely unverified.

Broadening it turned into a small lesson about what “testing an option” actually requires.

The easy 80%

Custom width and height was straightforward: generate a card at 600x400 instead of the 1200x630 default, then check the output PNG’s real pixel dimensions via sharp(png).metadata(). Not “did it render,” but “is the width literally 600.”

const png = await generateCard(markup, { width: 600, height: 400 });
const { width, height } = await sharp(png).metadata();
assert.equal(width, 600);
assert.equal(height, 400);

I went back and added the same dimensions check to the original smoke test too. It had been checking “is this a valid PNG of non-trivial size” - true, but it would have stayed true even if width and height were quietly ignored.

The part that made me stop

The fonts option is supposed to let a consumer override the bundled default font entirely. My first instinct for testing it: call generateCard with a custom font, check it still produces a valid PNG. Done, right?

Except that test would pass identically whether or not the option did anything. If fonts were silently ignored and the function fell back to its bundled default every time, the output would still be a valid PNG, because the bundled default works. A test built entirely from “does this run without crashing” can’t distinguish “the code path executed” from “the code path was skipped and something else compensated.”

The test that proves something needs a case where the two behaviors diverge. I don’t have a second real bundled font to swap in for a positive test, but I do have a way to force divergence: pass an empty fonts array.

await assert.rejects(() => generateCard(imperfectSystemsCard, { fonts: [] }));

Satori requires a font to render any text at all. If fonts: [] reaches Satori, rendering text-heavy markup throws. If the option were being silently ignored and the bundled default used instead, nothing would throw and the card would render fine. So this test can only pass if the override genuinely reaches the rendering call. A small, almost backwards-feeling test - asserting failure, not success - but it’s the one that discriminates between “works” and “looks like it works.”

The general shape of the mistake

“Pass an option, confirm nothing crashed” tests your happy path’s resilience, not the option. The tell is asking: if I silently deleted this feature and made the option a no-op, would this test still pass? If yes, it’s not testing the feature. For overrides and fallbacks specifically, the fix is almost always to construct an input where the fallback and the override would visibly disagree, then assert on which one actually happened.

Also in this batch

OgMeta’s image dimensions were hardcoded to 1200x630 even though a consumer might pass a differently-sized image; now they’re optional props defaulting to those values, plus twitter:image:alt (previously only the og: variant existed) and article:published_time/article:modified_time for blog posts. And a README note making explicit that pulling in native sharp even for meta-tags-only consumers is a deliberate tradeoff rather than an oversight. Card generation is the point of the package, and a second dependency-free package for the rare meta-tags-only case isn’t worth maintaining.

Related reading