Back to BlogQuality & Testing

The Test Pyramid for a SaaS: How Many Unit, API and E2E Tests

Rupak Amin

Founder & Lead Engineer, RAITHub

13 min read

For a SaaS, aim for roughly ten unit tests for every end-to-end test, with a strong API layer in between. Unit tests cover pricing, plan limits and business rules. API tests cover permissions, tenant isolation, billing webhooks and every endpoint's contract. End-to-end tests cover only the five to fifteen journeys that earn money: sign-up, sign-in, the core action, upgrade and invite.

If you would rather have the suite built and run for you, see how RAITHub would test this near the end. The rest of this guide is the method, so your own team can shape the pyramid without outside help.

What is the test pyramid, in plain terms?

The test pyramid is a way to decide how many tests to write at each level. Martin Fowler's site sums it up in two rules: write tests with different granularity, and the more high-level you get, the fewer tests you should have (Ham Vocke, The Practical Test Pyramid, martinfowler.com). At the bottom sit many small, fast tests. At the top sit a few slow tests that drive a real browser through the whole product.

The shape exists because cost rises as you go up. A unit test runs in milliseconds and points at one function when it fails. An end-to-end test takes seconds, needs a running app, a database and a browser, and can fail for reasons that have nothing to do with your change. Google's testing blog suggested a starting split of about 70% unit, 20% integration and 10% end-to-end, while noting that the exact mix differs by team (Google Testing Blog, Just Say No to More End-to-End Tests).

Some teams prefer the "testing trophy", which puts static checks at the base and the most weight on integration tests, on the principle that "the more your tests resemble the way your software is used, the more confidence they can give you" (Kent C. Dodds, The Testing Trophy). For a SaaS the two views meet in the middle: the API layer is where most of the risk lives, so it deserves more tests than the classic pyramid drawing suggests.

How many unit, API and end-to-end tests should a SaaS have?

There is no correct number, but there is a useful ratio and a useful ceiling. Keep end-to-end tests in the low dozens, let the API layer cover every endpoint and role, and let unit tests grow with your business rules.

LayerShare of tests (guide)Typical run timeWhat it proves
Static checks (types, lint)Not counted; always onSecondsThe code compiles and follows the rules
UnitAbout 60–75%Milliseconds eachEach rule computes the right answer
API and integrationAbout 20–30%Tens of milliseconds to a second eachEndpoints, database, permissions and tenancy work together
End-to-endAbout 5–10%, capped in the low dozensSeconds eachA real user can complete the journeys that matter

The percentages are RAITHub's working guide, not an industry standard. One real example: PropDesk, the property management platform RAITHub built, has 1,024 automated tests, of which 932 are unit tests and 92 are end-to-end. That is roughly ten to one, and it keeps a large suite fast enough to run on every change.

What belongs in SaaS unit tests?

Anything you can describe as "given these inputs, the answer is X" without a database or a network call. In a SaaS that is more than people expect:

  • Pricing and proration. Mid-cycle upgrades, downgrades, discounts, tax and currency rounding.
  • Plan limits and entitlements. Seat counts, usage caps, which features each plan unlocks. See enforcing plans and limits in code for the design.
  • Permission rules written as pure functions: can this role do this action on this resource.
  • Validation schemas for every form and API input.
  • Date and time logic: trial ends, renewal dates, time zones and daylight saving.
  • State machines: subscription status, invoice status, onboarding steps.

Write these as tables of cases so adding a new edge case is one line:

// src/billing/limits.test.ts
import { describe, it, expect } from 'vitest'
import { canAddSeat } from './limits'

describe('canAddSeat', () => {
  it.each([
    { plan: 'starter', seats: 2, expected: true },
    { plan: 'starter', seats: 3, expected: false },
    { plan: 'team', seats: 24, expected: true },
    { plan: 'team', seats: 25, expected: false },
    { plan: 'enterprise', seats: 5000, expected: true },
  ])('$plan with $seats seats -> $expected', ({ plan, seats, expected }) => {
    expect(canAddSeat(plan, seats)).toBe(expected)
  })
})

What belongs in the API layer, and why is it the SaaS-specific one?

The API layer is where a SaaS fails in ways that hurt most: one tenant seeing another tenant's data, a role doing something it should not, or a payment webhook updating the wrong account. These bugs live in the joins between code, database and auth, so unit tests cannot see them and end-to-end tests are too slow to cover every combination.

Run these tests against a real database (a container or a throwaway branch), not mocks. For each endpoint, cover:

  1. Tenant isolation. A user from tenant A gets a 404 or 403 for tenant B's records, on reads, updates, deletes and list filters. The guide to cross-tenant data leaks shows how these bugs usually happen.
  2. Role matrix. Every role against every sensitive action, generated from the same permission table the app uses.
  3. Contract. Status codes, response shape and error format, so the front end and any public API clients do not break.
  4. Billing events. Replay signed webhook payloads and check the account changes once, even when the same event arrives twice.
  5. Input abuse. Oversized payloads, wrong types, missing auth, expired tokens.

Playwright's built-in request fixture can test a server API directly, prepare server-side state before a browser test, and check server-side results afterwards (Playwright API testing). That lets one tool cover both the API layer and the end-to-end layer.

// tests/api/tenant-isolation.spec.ts
import { test, expect } from '@playwright/test'
import { tokenFor } from './helpers'

test('a user cannot read an invoice from another tenant',async ({ request }) => {
  const token = await tokenFor('alice@tenant-a.test')
  const res = await request.get('/api/invoices/inv_tenant_b_001', {
    headers: { Authorization: 'Bearer ' + token },
  })
  expect(res.status()).toBe(404)
})

Return 404 rather than 403 for another tenant's record, so the response does not confirm the record exists. The broader method for this layer is in the API testing guide.

Which journeys deserve an end-to-end test?

Only the ones where a failure would cost you customers or money that same day. For most B2B SaaS products that is a short list:

  • Sign up, verify email and land in an empty workspace
  • Sign in, including SSO if you sell it, and sign out
  • The core action your product exists for, end to end
  • Upgrade from trial or free to paid, with a test card in test mode
  • Invite a teammate and have them accept
  • Export or download the customer's own data

Each of these should be one test that reads like the journey, with setup done through the API rather than by clicking. If an end-to-end test checks a pricing rule or a validation message, move that check down to a unit or API test. Fowler's rule applies: if a higher-level test fails without a lower-level test failing, you need a lower-level test, and you should push tests as far down the pyramid as you can (The Practical Test Pyramid).

How do you run the pyramid in CI?

Run the cheap layers first and stop early. A failing type check should not wait for a browser to boot.

# .github/workflows/test.yml
name: test
on: [pull_request]
jobs:
  static-and-unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npx tsc --noEmit
      - run: npx eslint .
      - run: npx vitest run
  api:
    needs: static-and-unit
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17
        env: { POSTGRES_PASSWORD: test }
        ports: ['5432:5432']
    env:
      DATABASE_URL: postgresql://postgres:test@localhost:5432/postgres
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npx prisma migrate deploy
      - run: npx playwright test tests/api
  e2e:
    needs: api
    runs-on: ubuntu-latest
    strategy:
      matrix: { shard: [1, 2] }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test tests/e2e --shard=${{ matrix.shard }}/2

The e2e job assumes a webServer entry in playwright.config.ts that starts the app. Replace the Prisma line with your own migration command. For how these tiers fit around each deploy, see regression testing after every deploy.

What are the common ways a SaaS test suite goes wrong?

ShapeSymptomFix
Ice-cream cone (mostly end-to-end)CI takes 40 minutes; red builds get rerun until greenMove rule checks into unit tests; keep only journeys at the top
Hourglass (unit and e2e, no API layer)Tenant leaks and role bugs reach production despite "good coverage"Add API tests for isolation and the role matrix
Mock-heavy middleTests pass, production fails on a real query or constraintRun API tests against a real database
Flat pyramid (unit only)Every function is tested; sign-up is broken after a config changeAdd five to ten end-to-end journeys and post-deploy smoke tests

Flaky end-to-end tests are the most common reason teams stop trusting the top of the pyramid. The flaky e2e tests guide covers how to find and fix them, and Playwright vs Cypress in 2026 covers the tool choice.

How long does it take to build this yourself?

For a small SaaS with no tests, expect about two to four weeks of one engineer's time to get static checks, a first unit layer for billing and permissions, an API layer for tenant isolation, and five end-to-end journeys gated in CI. That estimate assumes someone who already knows your stack and a test runner such as Vitest or Playwright. The main risk of doing it yourself is spending that time on easy unit tests while the API layer, where tenant and role bugs hide, stays empty. Start from how to set up QA for an early-stage SaaS if you are at zero.

Buy, build or hire?

OptionWhat you getChoose this when
A tool or SaaS testing platformRecorded or low-code browser tests, hosted runs, dashboardsYou need a few end-to-end checks fast and have no one to write code tests; it will not give you unit or API layers
Freelancers or crowdtestingManual and exploratory passes, sometimes scriptsYou need extra eyes before a launch; nobody owns the suite afterwards
An in-house QA hireSomeone who learns your product deeply and owns qualityYou ship every week, can keep one person busy full time, and can manage QA work
A managed QAaaS teamA team that designs and runs the whole pyramid, managed by the providerYou want the suite built and maintained without hiring or managing testers yourself

Why RAITHub for this

RAITHub writes tests as part of every build, and the numbers are public: 1,024 tests on PropDesk, 530+ on Sundor Skin (where CI replays all 76 migrations), 750+ on TheSkinProof (the founder's own venture, not a client), and 400+ on this website. The QA and test automation service applies the same layering to an existing product, starting with the API layer where SaaS products usually have the biggest gap.

When you don't need us

  • Your product is pre-revenue with a handful of users. Static checks, unit tests for billing and three end-to-end journeys are enough. Write them yourself.
  • You already have a balanced suite under ten minutes. Keep adding a test with every bug fix and you will stay in shape.
  • You need a certified test lab or compliance attestation. RAITHub is not SOC 2 or ISO 27001 certified and does not issue attestations.

How RAITHub would test this

  • Scope: audit the current suite and map it against the pyramid; add the API layer for tenant isolation, the role matrix and billing webhooks; cap and stabilise the end-to-end set; wire the layers into CI in the order above.
  • Ways to buy it: a fixed-price one-off audit with a written report, a monthly QA plan, or a dedicated QA team that RAITHub manages and bills monthly. RAITHub does not place testers under your management.
  • Timeline: fixed in the written quote after the audit, based on the size of your API and how many journeys you sell on.
  • What you receive: tests and CI in your repository, a short handover document on how to add tests at each layer, IP assigned to you, and an NDA as standard.
  • Next step: a free 15-minute technical audit, then a fixed written quote.

See the full offer on the QA as a service page, or book the free 15-minute technical audit.

Documentation checked on 7 October 2026.

Frequently asked questions

What is a good ratio of unit to end-to-end tests?

As a working guide, about ten unit tests for each end-to-end test, with an API layer in between. Google's testing blog suggested 70% unit, 20% integration and 10% end-to-end as a starting point, adjusted per team.

Is the testing trophy better than the test pyramid for SaaS?

They agree more than they differ. Both keep end-to-end tests few. The trophy puts more weight on integration tests, which suits a SaaS because tenant isolation, permissions and billing bugs live in that middle layer.

How many end-to-end tests does a SaaS need?

Usually five to fifteen at first: sign-up, sign-in, the core action, upgrade, invite and data export. Keep the total in the low dozens and move rule checks down to unit or API tests.

Should API tests use a real database?

Yes. Tenant isolation, constraints and row-level security only show up against a real database. Use a container or a throwaway database branch in CI, and run migrations before the tests.

How do I test tenant isolation?

Sign in as a user from one tenant and request another tenant's records by ID, on every read, update, delete and list endpoint. Expect a 404, and generate the cases from your list of tenant-scoped resources so new ones are covered.

What test coverage percentage should a SaaS aim for?

Coverage is a weak target on its own. Aim for full coverage of billing, permissions and tenant-scoped endpoints, and accept lower numbers on simple display code. A missing API isolation test matters more than a coverage figure.

test pyramidsaas testingunit testsapi testingend-to-end teststest automation strategy

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.