I Only Test Happy Paths and Don’t Mock APIs
My E2E tests only cover happy paths. No error states, no loading spinners, no mocked APIs, no fixtures. Just the core user flows that actually matter.
And I ship faster than I did with “proper” test coverage.
The Setup
My test environment is a sandbox, completely isolated from production. It has its own database that resets on every deploy.
When I push code, tests run against the actual application. Real frontend, real backend, real database. Just in a separate environment that has nothing to do with production.
The database wipes clean after each run, so every test starts from scratch.
[IMAGE: Simple diagram showing Frontend → Backend → Database with “DB resets on deploy” annotation]
The whole app is dockerized, so spinning up this test environment anywhere is trivial. Same containers, same configuration, whether it’s my laptop or the CI runner.
You’ll need some DevOps knowledge to set this up properly: things like blue-green deployment strategies to avoid downtime, or you can go the easy route with something like Coolify to handle deployments for you.
For writing tests, I use Playwright’s recorder. Open the browser, click through the user flow, Playwright generates the test code. Save, commit, done. No fixtures. No mock responses.
[GIF: Recording a Playwright test using codegen - showing clicks being converted to test code in real-time]
Everything runs on a self-hosted GitHub Actions runner on a $10/month VPS. Completely separate from production infrastructure. It just runs tests when I push code.
Since I’m the only developer, I have the runner set to queue mode—one test run at a time. For a small team, this scales fine. You’re just waiting a few minutes for the queue. If you need more concurrency, spin up more runners or use GitHub’s paid runners and run multiple test environments in parallel. The dockerized setup makes that easy.
What I Actually Test
Every critical user flow end-to-end. The stuff that needs to work or the app is broken. Authentication, creating and editing resources, permissions, payment processing.
If these flows break, users can’t use the app or can’t pay. That’s what gets tested.
What I Don’t Test
Loading states? Nope. I build them, but I don’t test them. I trust the isLoading prop.
Error boundaries? Nope. They exist in the code, but no E2E tests for them.
[IMAGE: Side-by-side comparison - “What I Test” (happy path checkmarks) vs “What I Don’t Test” (crossed out edge cases)]
Why No Mocks?
I have a monolith. Frontend and backend in the same repo, deployed together. Why mock my own API when I can just run it in a test environment?
Mocks give you false confidence. Your test passes because your mock returns exactly what you told it to return. Then you deploy and the real API returns something different. Maybe a field is null, maybe the shape changed, maybe there’s a new validation. Your tests passed. Your app is broken.
Testing the whole stack catches the bugs that actually matter. API changes that break the frontend. Database constraints. Auth issues. With mocks? You find this in production.
Why Only Happy Paths?
The app shape can change drastically. I’m still finding product-market fit. Features get reworked or removed. Why write detailed tests for something that might be gone in two months?
What actually breaks production? I broke authentication. I modified an API endpoint. I added a database constraint that rejects what used to be valid. Those bugs stop users from signing up, using core features, or paying. A broken loading spinner is annoying. A broken signup flow means zero revenue.
Work vs Side Project
At work, I do the proper thing. Full test coverage. Mocked APIs so frontend tests don’t depend on backend. Separate integration tests. Error states tested. Loading states tested. Retries, timeouts, every edge case.
CI pipeline runs for 20 minutes. We pay for cloud CI compute. We get Slack messages about flaky tests. We maintain fixtures. When the API changes, we update mocks to match.
For my SaaS? The setup I described above. Pipeline runs in less than 10 minutes.
[SCREENSHOT: GitHub Actions workflow run showing < 10 minute completion time]
The work setup makes sense for a team. Multiple people on frontend and backend, mocked tests let them work independently. Compliance requirements need that coverage. Downtime costs millions, test everything.
But solo dev with a monolith? Overkill. I’m not coordinating across teams. I’m not satisfying auditors. I’m shipping a product that works.
The Tradeoffs
My tests are slower than mocked tests. Seconds instead of milliseconds. They hit a real database, make real HTTP requests, run real backend code.
But the whole suite runs in less than 10 minutes. Fast enough. Push code, know if it’s broken before I merge.
Need network? Yes. CI has network.
Can I test edge cases? Sure, seed specific data if needed. Rarely need to.
Concurrency? One runner in queue mode works for me. For a small team, you’re just waiting a few minutes. Need more? Spin up more self-hosted runners or use GitHub’s paid runners. The dockerized setup makes it easy to run multiple test environments in parallel.
Will this scale to a big team? Eventually you’ll hit limits. But it’ll handle way more than most people think.
When This Doesn’t Work
Microservices? Gets messy. Multiple services, coordinating state, eventual consistency. Mocks start making sense.
Separate frontend/backend teams deploying independently? Isolated tests let each team move without blocking.
Mission-critical systems where bugs cost lives or millions? Test everything.
Big QA team and unlimited budget? Go wild.
Small team with a monolith, building something where bugs are annoying but not catastrophic? Test what matters.
Results
This approach is context-dependent. Early-stage SaaS, solo dev, finding product-market fit. Your situation might be different.
Been running this for months. Caught bugs that would’ve broken core features: auth issues, data validation failures. Bugs that stop users from using the app.
Shipped features I wouldn’t have time for if I was writing tests for loading spinners and maintaining mocks.
Zero production incidents from untested edge cases so far. Could that change? Sure. I’ll adapt if it does.
Low maintenance costs.
[IMAGE: Simple metrics graphic - “X bugs caught” | “Low maintenance” | ”< 10 min pipeline” | “0 incidents”]
Sometimes good enough is actually good enough. You don’t need perfect coverage. You don’t need every best practice. You need tests that catch bugs that matter, run fast enough that you’ll actually run them, and cost little enough that you don’t think about it.