DEV Community

Royce
Royce

Posted on • Originally published at starterpick.com

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
Enter fullscreen mode Exit fullscreen mode

E2E Test Example


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/);
});
Enter fullscreen mode Exit fullscreen mode

Integration Test Example


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();
  });
});
Enter fullscreen mode Exit fullscreen mode

MSW Mocking


  // 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 }));
  }),
];
Enter fullscreen mode Exit fullscreen mode

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:

  1. Auth regressions — Changed a middleware? Tests verify protected routes still redirect.
  2. Billing bugs — Modified webhook handling? Tests verify subscription state transitions.
  3. Form validation — Updated validation rules? Tests verify error messages appear correctly.
  4. Database schema changes — Added a field? Tests verify queries still work.
  5. 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
Enter fullscreen mode Exit fullscreen mode

Priority 2: Integration Tests (Week 2)

- [ ] User model CRUD
- [ ] Subscription webhook handling
- [ ] Auth middleware
- [ ] API input validation
Enter fullscreen mode Exit fullscreen mode

Priority 3: Component Tests (Week 3+)

- [ ] Form components
- [ ] Navigation (auth state)
- [ ] Error boundaries
- [ ] Loading states
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)