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