skip to content
vinny neves
back

what survives when everything changes

4 min read
cinematic illustration of a shih tzu sitting on a black banner with the code 'expect(output).toBe(correct).' in cyan, surrounded by floating panels of jquery code, deprecated framework labels and stack trace fragments, bathed in a warm ember-orange light
thirteen years of frontend. jquery, angular 1, react, vue, ai, agents. everything changed. the question stayed the same: given this input, is the output what i expect?

hey o/

the other day i stumbled on an old article of mine about testing in vuejs. a good few years old. the example was a quantity component in a shopping cart, two buttons and an input. the kind of simple thing that swaps frameworks every five years but keeps raising the same question: what’s worth testing?

the answer i gave back then was — test the contract, not the implementation. treat the component as a black box, simulate the user’s interaction, check the output.

that answer survived everything.

2012 and the smell of burning plastic

i started working as a dev around 2012. the browser wars were still hot — you’d open ie’s devtools and catch a whiff of burning plastic, like the browser itself was begging you to quit. jquery ruled absolute. angular 1 was rolling in with that two-way data binding that felt like witchcraft.

and tests? testing was a backend thing. on the frontend it was “open the browser and see if it works”.

the truth is that most frontend projects from that era didn’t have a single test. and we slept fine, because we didn’t know what we were missing.

the framework rollercoaster

since then the ecosystem has spun around three full times. angular became angularjs became angular 2+ became “wait, which version are we on?”. react showed up and changed the entire conversation about componentization. vue arrived with a softer pitch and picked up a passionate community. each cycle dragged its own testing tools along — karma, jasmine, mocha, jest, vitest, testing library. the names changed, the apis changed, the setup changed.

but the fundamentals stayed put.

that vue article i wrote used jest and vue test utils. if i rewrote it today, i’d probably reach for vitest and testing library. the setup would be different, the imports would be different, the component might even be composition api instead of options api. but the test itself — mount, click, check the value — would be identical. because the component’s contract didn’t change.

and now there’s ai in the loop

this is the part i find most worth chewing on. we’re living through a moment where ai agents write code, review prs, generate tests automatically. tools like claude code pick up a component and spit out a test suite in seconds. mcp wires language models to any external service like it’s an npm install — if you haven’t read it yet, i wrote about that here on the blog.

and you know what happens when you ask an ai to generate tests without telling it what should actually be tested? it tests implementation. it tests that the increment method exists, that data() returns the initial state. the kind of brittle test that breaks the moment you refactor without changing any behavior.

the difference fits in two lines:

// implementation test — brittle
expect(wrapper.vm.increment).toBeDefined()

// contract test — resilient
await user.click(incrementButton)
expect(input).toHaveValue(1)

the first breaks if you rename the method. the second only breaks if the button stops working.

what survives

thirteen years in, what i carry with me is pretty simple. the tools change all the time — and they should change, because the ecosystem keeps evolving. the browser wars are over (sort of). jquery retired. frameworks come and go. ai joined the development loop and isn’t leaving.

but the question stays the same: given this input, is the output what i expect?

it doesn’t matter whether the component is vue 2 with options api, react 19 with server components, or a vanilla web component. it doesn’t matter whether the test was written by you or by an agent. what matters is whether the test validates the behavior the user actually perceives. the rest is implementation detail — and implementation detail changes next sprint.

that bug from the original article is the perfect illustration. the increment button resolved 1 + 1 and returned 11, because the input emitted text and js concatenated instead of summing. the test caught the bug not because it knew how the method worked under the hood, but because it simulated what the user would do and checked the result. black box. contract respected. bug found.

if you’re just starting out and feel overwhelmed by everything you need to learn — frameworks, bundlers, ai, mcp, agents — breathe. the foundations don’t move as fast as they seem to. learn to test behavior, not implementation. you’ll swap the rest as the landscape demands.

and if you’ve been on the road for a while, it might be worth revisiting that old test you wrote. odds are the framework doesn’t even exist anymore, but the logic behind it still holds.

thirteen years later, that cart component is still simple. two buttons, an input, and the same question as always: what really matters here?

the framework got old. the principle didn’t.

i’m always on linkedin, github and bluesky.

live long and prosper \o


share this post:

testing in production

an occasional newsletter on claude code, ai-assisted dev, and what it's like to teach code for a living. hosted on linkedin — you subscribe there and it lands in your feed and inbox.

subscribe on linkedin

next post
git worktree: the feature agents were waiting for