A side project needs less testing than you fear and more than you have: a handful of automated tests on the paths that would really hurt if they broke (signup and payment, saving and deleting user data, the one job people come for, anything that emails users), one or two end-to-end tests of the main flow, all of it running on every push, and a 10-minute manual checklist before each release. Everything else you skip on purpose and write down that you skipped it. A suite you still run in six months beats a perfect one you abandon in three weeks.
This is for people building a small web app on nights and weekends around a day job. Tool facts are as of October 2026 and link to the docs I read. The worked example is made up and labelled. If you are still deciding what the product does, start with scoping a side project MVP you can ship on weekends; testing a scope you have not chosen is wasted effort.
Why do "no tests" and "full coverage" both fail on a side project?
"No tests" feels fine for a month. You remember every corner and click through the app after each change. Then you take two weeks off, change the signup form on a Tuesday night, and find out on Saturday that nobody could create an account all week. On a side project the gap between breaking something and noticing is long, because you are not looking at the app all day. Tests shrink that gap to minutes.
"Full coverage" fails the other way. You copy habits from work, chase a number, and spend scarce evenings testing getters, layout and admin screens. The suite gets slow, a few tests fail at random, and one night you switch CI off "for now". You now pay for a big suite and get the safety of none.
The middle path: decide how much testing a side project needs by risk, not habit. Test the few things where a bug costs money, data or trust. Let you, your users and your monitoring catch the rest.
What would actually hurt if it broke?
Before writing a test, spend ten minutes listing what would really hurt. Not "what could have a bug", because everything could. What would lose you money, someone's data or a user's trust. For most small web apps:
The money path. Signup, checkout, and whether paying customers get what they paid for. A broken paid-access check either gives the product away or locks out people who paid.
The data-loss path. Saving, editing and deleting user data. A silent failed save or a delete that hits the wrong rows cannot be apologised away.
The one core job. The thing people come for: generating the invoice, booking the slot, converting the file.
Anything that emails users. Reminders, digests, receipts. A loop bug can send one person forty emails, or send someone else's details.
Who can see what. Can user A ever see user B's data? One test per rule is cheap insurance.
Settings pages, the marketing site, admin views only you use, colours and spacing are low risk. You will see them break, and fixing them later costs little.
What should you test first?
Turn the list into a small test budget. Rate each area on two things: how bad a break is, and how often you change that code. High on both means test it now. High risk but rarely touched means one test. Low risk and changing often means a manual check. Here is a starting table for a typical small paid web app; the ratings are my judgement, not research, so redo them for your product.
Area | If it breaks | How often you change it | What to do |
|---|---|---|---|
Signup and login | High: nobody gets in | Rarely | One end-to-end test of the happy path |
Checkout and paid access | High: lost money or angry customers | Sometimes | Integration tests on who gets access, plus one end-to-end payment in test mode |
Core job logic | High: the product is broken | Often | Unit tests on the logic, one integration test that saves the result |
Saving and deleting data | High: data loss | Sometimes | Integration tests against a real test database |
Emails and scheduled jobs | Medium to high: spam or silence | Rarely | Unit test on "who gets what", manual look at the template |
Settings and profile pages | Low | Sometimes | Manual checklist only |
Marketing pages and layout | Low | Often | No automated tests; look before release |
Your first budget is the top four or five rows. If that comes to more than about 20 tests, you are probably testing details instead of paths.
Which kind of test should you use for which job?
Ham Vocke's article The Practical Test Pyramid, on Martin Fowler's site, traces the test pyramid to Mike Cohn and boils it down to two rules: write tests at different levels of detail, and the higher the level, the fewer tests.
Unit tests for pure logic. Input in, answer out: parsing an uploaded file, date and time zone math, credits left, who gets today's reminder. They run in milliseconds, so cover the odd cases too, like a leap day or an empty file.
A few integration tests against a real test database. A save actually saves, a delete removes only the right rows, a user cannot read another user's records. Use the same kind of database you run in production; the pyramid article itself notes that an in-memory stand-in means testing against a database you do not ship.
One or two end-to-end browser tests. These click through the real app like a user. The article says to keep them to "a bare minimum", because they are slow and flaky, and to pick the most valuable user journeys. For a side project that is the main path, maybe signup.
For browser tests on a web app, Playwright is a sensible default. Playwright's installation guide describes an end-to-end test framework that runs Chromium, WebKit and Firefox, locally or in CI; npm init playwright@latest adds it to an existing project and offers to add a GitHub Actions workflow. By default it runs every test in all three browsers, so you may keep only Chromium at first.
Two habits from Playwright's best practices apply to any tool: test what the user sees (a named button, a message on screen) rather than CSS classes or function names, and do not test third-party services you do not control; fake their responses instead.
How small can your test data be?
Big fixture files are where side-project suites go to die: change one field and forty lines of fake data need editing.
Build data inside each test. A short helper like "a user with one paid plan" reads better than a shared file of fifty records.
Use the minimum. One user, one item, one payment. Add a second record only when the test is about two records, like A not seeing B's data.
Start every test clean. Wipe or roll back the test database between tests so leftovers cannot make another test pass or fail.
Fix the clock. Pass the date in or use a fake clock. Tests that read the real clock fail at month ends and daylight saving changes.
How do you run tests on every push without paying for it?
A test you must remember to run is one you will skip on a tired Wednesday. Let CI run it on every push. If your code is on GitHub, Actions is the obvious choice. According to GitHub's Actions billing docs, as of October 2026:
Standard GitHub-hosted runners are free in public repositories.
For private repositories, GitHub Free includes 2,000 minutes a month and GitHub Pro 3,000.
Linux runners are the cheapest; Windows and macOS cost more per minute.
With no payment method on file, usage is blocked once the quota runs out: no surprise bill, but your checks stop.
A small suite like this should finish in a few minutes, leaving room even if you push several times a night. Install only the browsers you test in CI, as Playwright suggests. Then make a red build block your deploy, and keep the run short enough that you wait for it. CI only tells you the code passed; what happens once it is live is covered in how to monitor a side project during your day job.
What goes in a 10-minute manual pre-release checklist?
Some things are not worth automating on a side project: how an email looks in a real inbox, how the page looks on your phone, whether the copy makes sense. Run a short checklist before any release that touches a risky area:
Sign up as a brand new user in a private window.
Do the core job once, start to finish.
Pay in your payment provider's test mode and confirm the paid feature turns on.
Edit and delete one record; confirm the right thing changed.
Trigger one email to yourself and read it on your phone.
Log in as a second test user and check you cannot see the first user's data.
Keep it in the repo next to the code. If an item keeps catching real bugs, automate it.
What should you do with a flaky test?
A flaky test sometimes passes and sometimes fails on the same code. It is worse than no test, because it teaches you to ignore red builds. Big teams fight this too: in a 2016 Google Testing Blog post, John Micco wrote that about 1.5% of their test runs reported a flaky result and almost 16% of their tests had some flakiness.
Martin Fowler's Eradicating Non-Determinism in Tests says to quarantine flaky tests, moving them out of the main suite so healthy tests keep a clear signal, then fix them quickly; he mentions limits such as no longer than a week in quarantine. My side-project rule is simpler: a flaky test gets one evening. If I cannot make it reliable, I delete it and add the step to the manual checklist.
Playwright retries are off by default. Playwright's guide to test retries explains that with retries on, a test that fails and then passes is reported as "flaky". Treat that label as a to-do, not a pass. Fowler's article lists the usual causes: tests that share data, async timing, remote services, the system clock and resource leaks. That is why the test data rules matter.
When should you add a new test?
After the first budget, add tests for a reason. The best reason is a bug: every bug you fix gets a regression test. Write a test that fails because of the bug, then fix the code until it passes. Ten extra minutes, and that bug never returns quietly. The pyramid article adds: if a high-level test catches a bug and no lower-level test failed, write the lower-level one too.
Other good reasons: you are about to change a risky area, or you are handing tasks to an AI coding tool, where tests are the guardrails that show whether its change broke something (more in using AI coding agents on a side project without the mess). Bad reasons: a coverage report says 60%, or a conference talk.
When does test coverage mislead you?
Coverage tells you which lines ran during tests, not whether the tests checked anything. Martin Fowler's note on test coverage calls it "a useful tool for finding untested parts of a codebase" but "of little use as a numeric statement of how good your tests are", and warns that high numbers are too easy to reach with low-quality tests.
Use coverage now and then as a map. Look at what is uncovered and ask: does this worry me? The payment check, yes. The settings page, no. Fowler's test for enough testing fits a side project well: bugs rarely escape to production, and you are rarely afraid to change code.
A made-up example: Ilsabet and Fermatabook
Everything here is made up: the person, the product and every number.
Ilsabet is a hospital pharmacist who builds Fermatabook on evenings and weekends: a booking page where independent piano teachers sell lesson packs, parents pay and book weekly slots, and the app emails a reminder the day before. In this story she had no tests until a time zone change moved a week of lessons an hour early after a daylight saving switch. She spent one weekend on this budget and saved it as a short testing note in the repo.
What she tests (made up) | Type | What she deliberately does not test |
|---|---|---|
Lesson credits: a pack adds credits, a booking uses one, cancelling over 24 hours ahead refunds it | Unit, 9 cases | Teacher profile page and photo upload (manual check) |
Weekly slot dates across daylight saving changes and month ends | Unit, 6 cases, including the original bug | Colours, fonts and layout of the booking page |
A booking saves and the same slot cannot be double-booked | Integration, real test database, 2 cases | The admin screen only she uses |
Parents see only their own bookings; teachers only their own students | Integration, 3 cases | The CSV export (manual check monthly) |
Deleting a student keeps past paid lessons in the teacher's records | Integration, 1 case | Calendar sync with outside apps (faked at the boundary, checked by hand) |
Tomorrow's reminder list has each booked parent once, and nobody else | Unit, 2 cases | Email wording and look (manual checklist) |
A parent books and pays in test mode; the teacher sees it in their schedule | End-to-end, Playwright, 1 test | Every browser except Chromium, for now |
That is 24 tests. In this story they run on every push in about three minutes, and the deploy waits for them. The right-hand column matters as much as the left: it records that skipping those was a decision, not something she forgot.
A one-weekend plan to get your first useful tests
One weekend is enough for a first suite you will keep. Timings are suggestions.
When | Do this | Done when |
|---|---|---|
Saturday, first hour | List what would hurt; fill in the test budget table | About five rows marked "test now" |
Saturday morning | Set up the test runner and a separate test database | One command runs the suite locally |
Saturday afternoon | Unit tests for the core logic and any date math | The odd cases you can think of are covered |
Saturday evening | Integration tests for save, delete and "A cannot see B" | Each runs against a clean test database |
Sunday morning | Add Playwright; one end-to-end test of the main path | It passes three runs in a row |
Sunday afternoon | CI on every push; a red build blocks the deploy | A deliberately broken test stops a deploy |
Sunday evening | Write the manual checklist and the "not tested on purpose" list into the repo | Both fit on one screen |
Every week after | Every bug fix gets a regression test | No bug comes back twice |
What are the common traps when testing a side project?
Testing what is easy instead of what is risky. Utility functions are fun to test. Start with the payment check.
Too many browser tests. Two good end-to-end tests beat ten shaky ones.
Testing against live services. A test should never charge a real card or email a real user. Use test mode and fakes.
Letting red builds become normal. Merge on red once and the suite stops meaning anything. Fix, quarantine or delete that week.
Mocking everything. If every test replaces the database, you are testing your mocks.
FAQ
Do I need TDD for a side project?
No. Writing the test first is a good habit for tricky logic and bug fixes, where a failing test proves you reproduced the bug. But a side project needs the risky paths covered and the suite running on every push, not a method.
Is 80% coverage a good target?
Not as a target. Fowler's note treats coverage as a way to find untested code, and expects thoughtful testing to land in the upper 80s or 90s as a side effect. I would rather have 40% with the money and data paths tested than 90% that misses the payment check.
Should I test the UI?
Test the main flow end to end and check that the right things appear on screen. Skip automated tests for layout, colours and copy; look at them yourself before release.
What about testing an AI or LLM feature?
Test the code around it like any other code; judging model output quality is a separate job (evals) and a separate topic.
Write the first test this weekend
Pick the one thing that would hurt most if it broke tonight, write one test for it, and make it run on every push. That single test is worth more than a testing plan you never start. When your side project is ready for more people, share it on SideHunt.