Skip to main content

Testing Strategy

TrickBook currently has zero tests across 98+ mobile source files and 24 backend route files. This document defines the testing strategy and the first tests to write.

Testing Stack

TrickList (Mobile)

ToolPurpose
JestTest runner (included with Expo)
React Native Testing LibraryComponent testing
@testing-library/jest-nativeAdditional matchers

Backend

ToolPurpose
JestTest runner
SupertestHTTP endpoint testing
mongodb-memory-serverIn-memory MongoDB for tests

Setup

TrickList

cd TrickList

npm install --save-dev jest @testing-library/react-native @testing-library/jest-native

Add to package.json:

{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"jest": {
"preset": "jest-expo",
"setupFilesAfterSetup": ["@testing-library/jest-native/extend-expect"],
"collectCoverageFrom": [
"src/**/*.{ts,tsx}",
"!src/**/*.d.ts"
],
"coverageThreshold": {
"global": {
"branches": 20,
"functions": 20,
"lines": 20,
"statements": 20
}
}
}
}

Backend

cd Backend

npm install --save-dev jest supertest mongodb-memory-server

Add to package.json:

{
"scripts": {
"test": "jest --runInBand --forceExit",
"test:watch": "jest --watch --runInBand",
"test:coverage": "jest --coverage --runInBand --forceExit"
},
"jest": {
"testEnvironment": "node",
"coverageThreshold": {
"global": {
"branches": 20,
"functions": 20,
"lines": 20,
"statements": 20
}
}
}
}

Priority: First 10 Tests to Write

These are ordered by risk. If something breaks here, users notice immediately.

Mobile (TrickList)

1. API Client (src/lib/api/client.ts)

The single point of failure for all API communication.

// src/lib/api/client.test.ts
import { apiClient } from './client';

describe('API Client', () => {
it('should attach auth token to requests', async () => {
// Mock authStore to return a token
// Verify Authorization header is set
});

it('should handle 401 by clearing auth state', async () => {
// Mock a 401 response
// Verify authStore.logout() is called
});

it('should handle network errors gracefully', async () => {
// Mock fetch to throw
// Verify error is caught and formatted
});
});

2. Auth Store (src/lib/stores/authStore.ts)

Controls login, logout, and session persistence.

// src/lib/stores/authStore.test.ts
import { useAuthStore } from './authStore';

describe('Auth Store', () => {
it('should start logged out', () => {
expect(useAuthStore.getState().user).toBeNull();
expect(useAuthStore.getState().isAuthenticated).toBe(false);
});

it('should set user on login', async () => {
// Call login with mock credentials
// Verify user state is set
});

it('should clear state on logout', async () => {
// Set up authenticated state
// Call logout
// Verify state is cleared
});
});

3. Trick Status Logic

Business logic for trick progression.

// src/types/trickbook.test.ts
describe('Trick Status', () => {
it('should calculate progress percentage correctly', () => {
// Test with 0/5 tricks landed
// Test with 3/5 tricks landed
// Test with 5/5 tricks landed
});
});

4-5. Two critical screen smoke tests

// Verify screens render without crashing
describe('HomeScreen', () => {
it('renders without crashing', () => {
render(<HomeScreen />);
});
});

Backend

6. Auth Endpoint (routes/auth.js)

// __tests__/auth.test.js
const request = require('supertest');
const app = require('../index');

describe('POST /api/auth', () => {
it('should return 400 with invalid credentials', async () => {
const res = await request(app)
.post('/api/auth')
.send({ email: 'bad@email.com', password: 'wrong' });
expect(res.status).toBe(400);
});

it('should return JWT token with valid credentials', async () => {
// Create test user, login, verify token
});

it('should reject requests without email', async () => {
const res = await request(app)
.post('/api/auth')
.send({ password: 'test' });
expect(res.status).toBe(400);
});
});

7. User Registration (routes/users.js)

// __tests__/users.test.js
describe('POST /api/users', () => {
it('should create a new user', async () => { /* ... */ });
it('should reject duplicate email', async () => { /* ... */ });
it('should hash the password', async () => { /* ... */ });
});

8. Auth Middleware (middleware/auth.js)

// __tests__/middleware/auth.test.js
describe('Auth Middleware', () => {
it('should reject requests without token', () => { /* ... */ });
it('should reject invalid tokens', () => { /* ... */ });
it('should set req.user for valid tokens', () => { /* ... */ });
});

9-10. Trick list CRUD and Spots endpoints

Core data operations that users depend on daily.

Test File Location

Colocated tests (test file next to source file):

src/
├── lib/
│ ├── api/
│ │ ├── client.ts
│ │ └── client.test.ts # Right next to the source
│ └── stores/
│ ├── authStore.ts
│ └── authStore.test.ts
├── components/
│ ├── ui/
│ │ ├── Button.tsx
│ │ └── Button.test.tsx

This makes it obvious when a file has no test, and keeps related code together.

Coverage Targets

Start low, ratchet up as you add tests:

PhaseTargetTimeline
Phase 120% linesFirst session
Phase 240% linesTwo weeks
Phase 360% linesOne month
Steady state70%+ linesOngoing

Coverage thresholds in Jest config will fail CI if coverage drops below the target.

What NOT to Test

  • Expo/React Native internals
  • Third-party library behavior
  • Pixel-perfect UI (use snapshot tests sparingly)
  • Implementation details (test behavior, not how it's done)

CI Integration

Tests run on every PR as part of the validate job. See CI/CD Pipeline.

- name: Run Tests
run: npm test -- --coverage --ci