Skip to main content

Best Boilerplate with Testing: Epic Stack vs T3 Stack vs Makerkit

·StarterPick Team
testingvitestplaywrightsaas boilerplatecomparison

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

ToolEpic StackT3 StackMakerkit
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

AreaEpic StackT3 StackMakerkit
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:

  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

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
ToolPurpose
VitestUnit + integration tests
PlaywrightE2E browser tests
MSWAPI request mocking
Testing LibraryComponent testing
FakerTest 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 →

Comments