The Testing Gap in SaaS Boilerplates
Here's a dirty secret about SaaS boilerplates: most ship with zero tests. The auth works? Tested manually. Stripe webhooks handle edge cases? Probably. Database migrations don't break? We'll find out in production.
Three boilerplates take testing seriously: Epic Stack (Remix) sets the gold standard with comprehensive E2E, integration, and unit tests. T3 Stack (Next.js) provides testing foundations with Vitest. Makerkit (Next.js) includes Cypress/Playwright E2E tests for critical paths.
Testing matters because SaaS boilerplates are starting points, not finished products. You'll modify auth flows, customize billing, add features — every change risks breaking what worked before. Tests catch these regressions before your users do.
TL;DR
Epic Stack (free, Remix) has the most comprehensive testing setup — Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for components, plus documented patterns for every test type. Makerkit ($249+, Next.js) includes Cypress E2E tests for auth and billing flows. T3 Stack (free, Next.js) provides Vitest configuration but minimal pre-written tests. Choose Epic Stack for testing excellence. Choose Makerkit for practical E2E coverage.
Key Takeaways
- Epic Stack's testing is unmatched — every feature has tests, every pattern is documented, every edge case is considered.
- Most boilerplates have zero tests. ShipFast, Supastarter, SaaSrock, LaunchFast — no test suites included.
- Epic Stack uses the modern testing stack — Vitest + Playwright + MSW + Testing Library. No Jest, no Enzyme, no outdated tools.
- Makerkit tests critical paths — auth flows, billing checkout, team management. Not exhaustive, but covers the risky areas.
- T3 Stack gives you the tools, not the tests. Vitest is configured, but writing tests is on you.
- Test patterns are more valuable than test files. Epic Stack teaches you HOW to test, not just what to test.
Testing Stack Comparison
| Tool | Epic Stack | T3 Stack | Makerkit |
|---|---|---|---|
| Unit test runner | ✅ Vitest | ✅ Vitest | ✅ Vitest |
| Component testing | ✅ Testing Library | ⚠️ Manual setup | ⚠️ Manual setup |
| E2E testing | ✅ Playwright | ❌ Not included | ✅ Cypress/Playwright |
| API mocking | ✅ MSW | ❌ | ❌ |
| Visual regression | ❌ | ❌ | ❌ |
| CI/CD integration | ✅ GitHub Actions | ⚠️ Basic | ✅ GitHub Actions |
| Test fixtures/factories | ✅ Custom helpers | ❌ | ⚠️ Basic |
| Database seeding | ✅ Prisma seed | ✅ Prisma seed | ✅ Prisma seed |
| Code coverage | ✅ Configured | ⚠️ Manual | ⚠️ Manual |
| Snapshot testing | ❌ (intentionally) | ❌ | ❌ |
Pre-Written Tests
| Area | Epic Stack | T3 Stack | Makerkit |
|---|---|---|---|
| Auth (login/signup) | ✅ Full E2E + unit | ❌ | ✅ E2E |
| Password reset | ✅ E2E | ❌ | ⚠️ Basic |
| OAuth flow | ✅ Mocked | ❌ | ✅ E2E |
| Stripe checkout | ❌ (no billing) | ❌ | ✅ E2E |
| Webhook handling | ❌ | ❌ | ⚠️ Basic |
| User profile CRUD | ✅ Integration | ❌ | ❌ |
| Form validation | ✅ Unit + integration | ❌ | ❌ |
| Error boundaries | ✅ Unit | ❌ | ❌ |
| Accessibility | ✅ axe-core integration | ❌ | ❌ |
| Route protection | ✅ Integration | ❌ | ✅ E2E |
| Database queries | ✅ Integration | ❌ | ❌ |
Epic Stack: Testing Done Right
Epic Stack's testing philosophy comes from Kent C. Dodds — the author of Testing Library and one of the most influential voices in JavaScript testing.
Test Structure
tests/
├── e2e/
│ ├── auth.test.ts # Login, signup, logout flows
│ ├── onboarding.test.ts # New user setup
│ ├── settings.test.ts # Profile and preferences
│ └── notes.test.ts # CRUD operations
├── integration/
│ ├── models/
│ │ └── user.test.ts # Database model tests
│ └── routes/
│ ├── auth.test.ts # Auth route handlers
│ └── notes.test.ts # Note route handlers
├── mocks/
│ ├── handlers.ts # MSW request handlers
│ └── index.ts
├── setup/
│ ├── setup-test-env.ts # Test environment config
│ ├── global-setup.ts # One-time setup
│ └── db-setup.ts # Database reset between tests
└── utils/
├── test-utils.ts # Custom render, helpers
└── factories.ts # Data factories
E2E Test Example
import { test, expect } from '@playwright/test';
test('user can sign up and complete onboarding', async ({ page }) => {
await page.goto('/signup');
// Fill signup form
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'SecurePass123!');
await page.click('button[type="submit"]');
// Verify redirect to onboarding
await expect(page).toHaveURL('/onboarding');
// Complete onboarding
await page.fill('[name="name"]', 'Test User');
await page.click('button[type="submit"]');
// Verify redirect to dashboard
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome, Test User')).toBeVisible();
});
test('protected routes redirect to login', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveURL(/\/login/);
});
Integration Test Example
import { describe, it, expect, vi } from 'vitest';
import { createUser, getUserByEmail } from '~/models/user.server';
import { prisma } from '~/utils/db.server';
describe('User Model', () => {
it('creates a user with hashed password', async () => {
const user = await createUser({
email: 'test@example.com',
password: 'password123',
});
expect(user.email).toBe('test@example.com');
expect(user.password).not.toBe('password123'); // Hashed
});
it('prevents duplicate emails', async () => {
await createUser({ email: 'dup@example.com', password: 'pass1' });
await expect(
createUser({ email: 'dup@example.com', password: 'pass2' })
).rejects.toThrow();
});
});
MSW Mocking
import { rest } from 'msw';
export const handlers = [
// Mock external email service
rest.post('https://api.resend.com/emails', (req, res, ctx) => {
return res(ctx.json({ id: 'mock-email-id' }));
}),
// Mock Stripe webhook
rest.post('/api/webhooks/stripe', (req, res, ctx) => {
return res(ctx.json({ received: true }));
}),
];
MSW intercepts HTTP requests during tests, providing reliable, fast test execution without hitting external services.
Why Testing Matters for Boilerplates
When you modify a boilerplate (and you will), tests catch:
- Auth regressions — Changed a middleware? Tests verify protected routes still redirect.
- Billing bugs — Modified webhook handling? Tests verify subscription state transitions.
- Form validation — Updated validation rules? Tests verify error messages appear correctly.
- Database schema changes — Added a field? Tests verify queries still work.
- API contract breaks — Changed a response shape? Tests verify clients handle the new format.
Without tests, every modification is a production experiment. With tests, you refactor with confidence.
Adding Tests to a Boilerplate That Has None
If you chose ShipFast, Supastarter, or any boilerplate without tests, here's a practical testing plan:
Priority 1: E2E Critical Paths (Week 1)
- [ ] User signup flow
- [ ] User login flow
- [ ] Password reset flow
- [ ] Stripe checkout flow
- [ ] Subscription cancel flow
- [ ] Protected route access
Priority 2: Integration Tests (Week 2)
- [ ] User model CRUD
- [ ] Subscription webhook handling
- [ ] Auth middleware
- [ ] API input validation
Priority 3: Component Tests (Week 3+)
- [ ] Form components
- [ ] Navigation (auth state)
- [ ] Error boundaries
- [ ] Loading states
Recommended Stack
| Tool | Purpose |
|---|---|
| Vitest | Unit + integration tests |
| Playwright | E2E browser tests |
| MSW | API request mocking |
| Testing Library | Component testing |
| Faker | Test data generation |
When to Choose Each
Choose Epic Stack If:
- Testing is a priority — you want a testing culture from day one
- You're learning testing — Epic Stack teaches testing patterns through real examples
- You're building for enterprise — enterprise customers often require test coverage
- Long-term maintenance matters — tests pay off as your codebase grows
- You use Remix — Epic Stack is the best-tested Remix boilerplate
Choose Makerkit If:
- You want practical E2E tests for critical paths without building everything yourself
- Next.js is your framework — Makerkit's tests work with Next.js App Router
- Critical path coverage is sufficient — auth + billing E2E covers the highest-risk areas
- You'll add more tests incrementally — start with Makerkit's foundation, extend over time
Choose T3 Stack If:
- You'll write your own tests — Vitest is configured, you provide the tests
- You have testing experience — you know what to test and how
- Flexibility matters — no pre-written tests means no test patterns to work around
The Real Cost of No Tests in a Boilerplate
Teams that choose an untested boilerplate often don't feel the cost until three to six months in. The first few weeks are fast — you're building features on top of infrastructure that mostly works. The cost appears when you start modifying the infrastructure.
Changing how sessions are stored? You'll find out whether your protected routes still work from users reporting they can't log in. Upgrading the Stripe SDK version? You'll find out whether your webhook handler still correctly parses events from production errors. Modifying the user model? You'll find out from a support ticket that profile updates are silently failing.
With tests, each of these scenarios surfaces in CI before deployment. Without tests, each scenario surfaces in production and requires manual verification plus a debugging cycle. For a solo founder or small team, that debugging overhead compounds — each undetected regression costs two to four hours to diagnose and fix, plus the cost of user trust.
The counterargument: tests slow you down in the early stages when you're still figuring out what to build. This is partly true. Writing comprehensive tests for a feature you'll remove in two weeks is waste. But tests for infrastructure — auth flows, billing webhooks, protected routes — are not early-stage concerns. These don't change shape as your product evolves. The effort to test them once is paid back every time you touch auth or billing code.
Testing Patterns Worth Adopting Regardless of Boilerplate
Even if you chose a boilerplate without tests, these three test patterns pay back their investment fastest:
The boilerplate and tool choices covered here represent the most actively maintained options in their category as of 2026. Evaluate each against your specific requirements: team expertise, deployment infrastructure, budget, and the features your product requires on day one versus those you can add incrementally. The best starting point is the one that lets your team ship the first version of your product fastest, with the least architectural debt.
Stripe webhook test fixtures. Create a test that sends a realistic customer.subscription.deleted Stripe event to your webhook handler and verifies the correct database state update occurs. This is the most financially critical code path in your SaaS and the hardest to test manually because simulating subscription state changes in Stripe's test mode is tedious.
Auth redirect tests. A single Playwright test that navigates to /dashboard while unauthenticated and verifies a redirect to /login. Then another that logs in and verifies access to /dashboard. These two tests catch 80% of auth regression issues.
Email delivery snapshot tests. If your boilerplate sends transactional emails, write a test that exercises the email service with test data and captures the rendered HTML. This catches broken template rendering before users receive malformed welcome emails or password reset flows that don't work.
Compare testing features across 50+ boilerplates on StarterPick — find a starter that ships with confidence.
See our best SaaS boilerplates guide for top-rated starters that prioritize code quality.
Review the boilerplate trap and technical debt guide to understand long-term maintenance costs.
Browse the free and open-source SaaS boilerplates guide — Epic Stack is free and has the best testing setup of any boilerplate.