Best Boilerplate with Testing: Epic Stack vs T3 Stack vs Makerkit
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
Compare testing features across 50+ boilerplates on StarterPick — find a starter that ships with confidence.
Check out this boilerplate
View Epic Stack on StarterPick →