Testing your vibecoded app (without being a testing pro)

Your app works in the demo: you fill in the form, you click, it's perfect. Then a real user shows up, submits an empty field, double-clicks "Send," loses their connection mid-way — and it falls over. Good news: you don't need to become a testing pro to cover those cases. The AI can write the tests and run them, and it's that second part that changes everything.

The demo is the easy path

A demo proves one thing: that it works when everything goes right. One user, clean data, the network responds, nobody clicks twice. That's the happy path, and generated code almost always nails it.

Bugs, though, live on the edges: the empty input, the unexpected format, the duplicate, the double-click, the network call that fails. Testing is nothing more than checking those edges automatically and repeatedly, so you don't have to re-click everything by hand after each change.

And no, it doesn't take a degree. A test is just a bit of code that says "when I do this, I should get that." The AI knows how to write them for your stack (Vitest or Jest for JS logic, Playwright to drive a real browser — the assistant picks what fits your project). Your role changes, but it stays simple — I'll get to it.

The real trick: RUN the tests, don't just write them

Here's the heart of it. Asking the AI to write tests is good. Asking it to run them is what unlocks the self-repairing loop.

A test that runs gives the AI a clear signal: red or green. A red test is a precise target — "here's exactly what's wrong, fix it." The model is great at that: write the code, run the tests, read the failure, fix, re-run, until green. When the assistant can run npm test itself, it loops on its own until things pass. That's far more reliable than you eyeballing whether the result "looks right."

Two honest guardrails, because it's not magic:

  • The AI can cheat — making a test pass by weakening it, or by hardcoding the expected answer, instead of fixing the real logic. A test like expect(true).toBe(true) is green and proves nothing.
  • Green doesn't mean "bug-free." It means "what I checked still works."

So here's the one reflex you keep: read the tests. Not to write them — to make sure they check something real. My little pro trick: deliberately break the code for a second and re-run — if the test stays green, it isn't testing anything. A good test must go red when the code is wrong.

What to test first

You don't test everything — chasing 100% coverage is a trap that just discourages you. You test what would hurt, and what you change often.

In order:

  1. The core flow. The one or two things your app exists for (sign up, create an item, pay). If that breaks, nothing else matters.
  2. The edges that trip up generated code. This is where your future bugs hide:
    • empty or missing input (blank field, nothing selected);
    • invalid input (wrong format, negative number, huge text);
    • duplicates (submitting the same thing twice);
    • double-click / double-submit (the request fires twice — two orders, two charges);
    • network error (the call fails or times out — does the screen crash, freeze, or handle it cleanly?).
  3. Any bug you've already hit. The moment you fix a real bug, add a test that locks it down: it's the highest-return test there is, it stops the bug from coming back.
  4. The tricky logic. Date math, money, rounding, permissions — anything with "ifs." Pure functions (one input, one output) are the easiest and most valuable to test.

The right question to ask: "what would embarrass me if a user hit it in the first hour?" Test that first.

The reflex: ask for tests with the feature

The habit that makes all of this free: ask for the tests at the same time as the feature, never after. Bolting tests onto finished code is a chore everyone puts off. Asking for them up front costs nothing.

In practice, your instruction looks like:

Implement adding a task. Write tests covering: empty field, duplicate, double-click, and a failed network call. Run them and fix until they pass.

Two bonuses on top. First, writing the tests alongside pushes the code to be testable — smaller functions, clear inputs and outputs. Second, your tests become a living description of the intended behavior: next session, the AI can re-run them to relearn what your app is supposed to do, instead of guessing again. It's memory that lives in the repo, not in the conversation.

One last simple habit: run the tests before you ship. A passing build tells you it compiles; passing tests tell you it still works.

In short

Testing a vibecoded app doesn't require being an expert: it requires having the AI write the tests, running them so it fixes itself, and keeping an eye out to confirm they actually test something. Start small — the core flow, a few edge cases, a test for every bug you hit — and ask for them with each feature. Letting the AI code fast and prove it holds: that's exactly what vibecoding like a pro looks like.