SalesSheet has 375 automated tests and zero failures. For a product built by a single developer, that might sound excessive. It is not. Those tests are the reason we can ship features daily without breaking existing functionality, refactor core systems without fear, and deploy to production at 5 PM on a Friday with full confidence. This post breaks down our testing philosophy, the tools we use, what we actually test, and why solo founders in particular should invest heavily in automated testing.
On a team, you have code reviews. A second pair of eyes catches the obvious mistakes, the edge cases you forgot, the function that silently returns null instead of throwing an error. A QA engineer runs through the user flows before release. A senior developer questions whether your refactor actually preserved the original behavior. When you are solo, none of those safety nets exist. You are the developer, the reviewer, and the QA team. Automated tests fill every one of those roles.
The psychology matters too. Without tests, every deployment carries anxiety. You push a change that fixes the pipeline kanban view and wonder if you accidentally broke the contact detail page. You refactor a utility function used in twelve places and spend twenty minutes clicking through the app to make sure nothing broke. That anxiety is a tax on your velocity. It makes you hesitant to refactor, reluctant to touch working code, and conservative about the changes you attempt. Tests eliminate that tax. When the suite passes, you know the app works. Not probably works. Works.
As a solo founder, your test suite is not just quality assurance. It is your co-founder, your code reviewer, and your QA team, all running in 45 seconds.
We use three tools, each serving a distinct purpose. Vitest handles unit and integration tests. It is fast, compatible with our Vite-based build system, and supports TypeScript natively. We have 131+ Vitest tests covering React component rendering, custom hook behavior, utility functions, and data transformation logic. Vitest runs the entire suite in under 15 seconds, which means we run it constantly during development without any friction.
Playwright handles end-to-end tests that exercise the full application stack. These tests launch a real browser, navigate to the app, click buttons, fill forms, and verify that the right things happen. Playwright tests are slower (they take about 2 minutes for the full suite) but they catch integration issues that unit tests miss: a button that renders correctly but does not trigger the right API call, a form that validates on the client but fails server-side, a real-time subscription that works in isolation but conflicts with another listener.
GitHub Actions ties everything together as our CI pipeline. Every push to any branch triggers the full test suite. Pull requests cannot be merged until all tests pass. This is non-negotiable — there is no "skip CI" escape hatch, no manual override. If the tests fail, the code does not ship. This discipline is especially important when you are solo because there is no one else to enforce it.
We test at multiple levels with different granularity depending on the risk. API routes and Edge Functions get the most thorough coverage because they handle business logic, data mutations, and third-party integrations. Every Supabase Edge Function that processes webhooks (Telnyx call events, email sync updates, Slack notifications) has tests that verify it handles valid payloads correctly, rejects malformed input, and handles third-party API failures gracefully.
React components are tested for rendering correctness and user interaction. We verify that the contacts grid renders the right columns, that the pipeline kanban correctly groups deals by stage, that the phone dialer shows the right UI states during a call. These are not snapshot tests (which we find brittle and noisy) but behavioral tests that assert specific DOM elements and interactions.
Authentication and authorization flows get dedicated tests because getting these wrong has severe consequences. We test that row-level security policies in Supabase correctly prevent users from accessing data in other organizations, that invitation flows properly scope new users, and that API routes reject unauthenticated requests. The organization sharing feature alone has over a dozen tests covering different permission scenarios.
Data transformations are tested with specific input-output pairs. Functions that parse email headers, format phone numbers, calculate deal probability, or transform API responses into display-ready data structures all have tests with known inputs and expected outputs. These are the most satisfying tests to write because they are pure functions with deterministic behavior.
Every test file mirrors the source file it covers. If the component lives at src/components/Pipeline/KanbanBoard.tsx, the test lives at src/components/Pipeline/KanbanBoard.test.tsx. This convention makes it trivially easy to find the tests for any given file and to verify that new files have test coverage. We use Vitest's describe blocks to group tests by behavior category and it blocks with descriptive names that read like specifications.
For Edge Functions, we created test helpers that mock Supabase client responses, simulate webhook payloads from Telnyx and other providers, and provide authenticated request contexts. These helpers reduced the boilerplate for writing a new Edge Function test from ~50 lines to ~10 lines, which dramatically lowered the friction of adding test coverage to new functions.
We also maintain a small set of fixture files with realistic test data — sample contacts, deals, email threads, and call records — that multiple test files reference. This avoids the problem of every test file defining its own slightly different mock data and ensures consistency across the test suite.
Tests are only valuable if they catch real problems. Here are a few concrete examples from the past month. A refactor of the contact search function broke fuzzy matching for names with accented characters — "Muguira" would match but "Mugüira" would not. A unit test with an accented fixture caught this immediately. A change to the real-time subscription logic caused duplicate entries to appear in the contacts grid when two users were viewing the same list. A Playwright end-to-end test that simulated concurrent sessions caught the race condition before it reached production.
The most expensive bug our tests prevented was an authorization regression. A refactor of the organization sharing queries accidentally removed a WHERE clause that scoped data by organization ID. Without the test suite, this would have leaked contact data between organizations — a catastrophic bug for a CRM. The authorization test suite caught it within seconds of the change being made. That single catch justified every hour we spent writing tests.
The most common objection to comprehensive testing is time. Writing tests takes as long as writing the feature, the argument goes, so you are halving your development velocity. This was true five years ago. It is not true with AI-assisted development. Claude Code generates test files from component source code, edge function logic, and utility functions. We describe the expected behavior in natural language, and Claude produces a complete test file with realistic fixtures, edge case coverage, and proper assertions.
The generated tests are not perfect — we review and adjust them, especially around edge cases and authorization logic. But they typically cover 80% of what we would have written manually, in about 10% of the time. The remaining 20% — the tricky edge cases, the concurrency scenarios, the authorization boundary tests — we write by hand because those are the tests that matter most and deserve human attention.
This AI-assisted approach to testing has fundamentally changed our calculus. The cost of testing is no longer a significant fraction of development time. It is a rounding error. And the value — confidence to refactor, freedom to deploy, protection against regressions — is enormous. If you are a solo founder and you are not writing tests because you think you cannot afford the time, I would argue the opposite: you cannot afford not to. Start with your most critical paths (authentication, data access, payment processing), use AI to generate the bulk of your test coverage, and build from there. Your future self, debugging a production incident at midnight, will thank you.