Testing
15 Nov 2026
10 min read

Testing Strategies: Unit, Integration, and E2E for Micro-Apps

How SpaceZero sets up Vitest for unit tests, where to add tests, and how to layer E2E coverage with Playwright when you need it.

Testing Strategies for Micro-Apps

SpaceZero ships Vitest (jsdom) for unit and component tests. There is no Playwright setup and no test:e2e script — E2E is opt-in.

Current Setup

code

vitest.config.ts # jsdom, globals, @ alias, coverage

src/test/setup.ts # jest-dom matchers, cleanup, matchMedia + serviceWorker mocks

src/components/__tests__/

└── button.test.tsx # the shipped example test

Scripts:

bash

npm run test # vitest run (one-shot)

npm run test:watch # watch mode

npm run test:coverage # run with coverage

Test Structure

Colocate new tests near the code they cover:

code

src/

├── components/

│ ├── __tests__/

│ │ └── button.test.tsx

│ └── ui/

│ └── button.tsx

├── hooks/

│ └── useAuth.test.ts # add your own

└── lib/

└── utils.test.ts # add your own

Component Test (the shipped example)

tsx

// src/components/__tests__/button.test.tsx

import { describe, it, expect, vi } from 'vitest';

import { render, screen } from '@testing-library/react';

import { Button } from '@/components/ui/button';

 

describe('Button', () => {

it('renders with children text', () => {

render(<Button>Click me</Button>);

expect(screen.getByRole('button')).toHaveTextContent('Click me');

});

 

it('renders as anchor when href is provided', () => {

render(<Button href="/test">Link button</Button>);

const link = screen.getByRole('link');

expect(link).toHaveAttribute('href', '/test');

expect(link).toHaveTextContent('Link button');

});

 

it('calls onClick when clicked', () => {

const handleClick = vi.fn();

render(<Button onClick={handleClick}>Click</Button>);

screen.getByRole('button').click();

expect(handleClick).toHaveBeenCalledTimes(1);

});

});

Hook and Utility Tests

typescript

// src/hooks/useAuth.test.ts (example)

import { describe, it, expect } from 'vitest';

import { renderHook } from '@testing-library/react';

import { useAuth } from './useAuth';

 

describe('useAuth', () => {

it('exposes the auth API', () => {

const { result } = renderHook(() => useAuth());

expect(result.current.signInWithEmail).toBeDefined();

expect(result.current.signOut).toBeDefined();

});

});

Integration Tests (Edge Functions)

Edge functions are plain request/response handlers — call them directly with a mocked Supabase client:

typescript

// functions/api/auth/check-user.test.ts (example)

import { describe, it, expect, vi } from 'vitest';

 

vi.mock('@/lib/supabase', () => ({

getAdminSupabaseClient: () => ({

auth: { admin: { listUsers: vi.fn().mockResolvedValue({ data: { users: [{ email: '[email protected]' }] } }) } },

}),

}));

 

describe('check-user', () => {

it('returns exists=true for a known email', async () => {

const request = new Request('http://test.com', {

method: 'POST',

body: JSON.stringify({ email: '[email protected]' }),

});

const { onRequestPost } = await import('./check-user');

const response = await onRequestPost({ request, env: {} });

expect((await response.json()).exists).toBe(true);

});

});

E2E Tests (Optional Playwright)

Playwright is not installed. To add it:

bash

npm init playwright@latest

# Select: TypeScript, tests folder, GitHub Actions

 

# then add to package.json:

# "test:e2e": "playwright test"

typescript

// test/e2e/auth.spec.ts

import { test, expect } from '@playwright/test';

 

test.describe('Authentication', () => {

test('sign up with email', async ({ page }) => {

await page.goto('/');

await page.click('button:has-text("Login")');

await page.fill('input[type="email"]', '[email protected]');

await page.fill('input[type="password"]', 'password123');

await page.click('button:has-text("Create Account")');

 

await expect(page).toHaveURL('/profile');

});

});

CI Integration

yaml

# .github/workflows/test.yml

jobs:

test:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- uses: actions/setup-node@v4

- run: npm ci

- run: npm run test

# add once Playwright is set up:

# - run: npm run test:e2e

Best Practices

  1. Test Behavior, Not Implementation
  2. Use accessible queries: getByRole, getByLabelText before data-testid
  3. Mock External Dependencies: Supabase, APIs, timers
  4. Test Error States: Network errors, validation errors
  5. Keep Tests Fast: Parallelize, avoid sleeps
  6. Deterministic Data: Use fixed timestamps, IDs

Coverage Targets

  • Unit: >80% for utilities, hooks
  • Integration: >70% for API functions
  • E2E (when added): Critical paths only (auth, purchase, core feature)