managarten/docs/TESTING_IMPLEMENTATION_GUIDE.md
Wuesteon ad495b2044 📝 docs: restructure CLAUDE.md and documentation (74% reduction)
Optimize CLAUDE.md based on industry best practices from HN and HumanLayer:

Changes:
- Trim CLAUDE.md from 678 to 176 lines (74% reduction, 5.7KB)
- Add "Critical Gotchas" section for common AI mistakes
- Add verification signature (🏗️ ManaCore Monorepo)
- Create docs/README.md navigation hub with "I want to..." index
- Delete 5 outdated audit files (ENV_AUDIT_*, DEPENDENCY_ALIGNMENT)
- Archive 7 analysis/historical docs to docs/archive/
- Keep authentication docs separate per request (.claude/guidelines/)

Benefits:
- Better AI instruction adherence (within ~150-200 line budget)
- Progressive disclosure via signposting to detailed docs
- Cleaner navigation with topic-based organization
- Reduced maintenance burden (stale docs archived)

Backup: CLAUDE.md.backup preserves original 678-line version
Change log: docs/archive/RESTRUCTURE_2025-12-16.md
2025-12-16 17:37:19 +01:00

12 KiB

Testing Implementation Guide

Quick Start Guide for Adding Tests to the Manacore Monorepo

Table of Contents

Quick Start

Prerequisites

# Ensure you have the correct versions
node --version  # Should be 20+
pnpm --version  # Should be 9.15.0

Install Dependencies

# From monorepo root
pnpm install

Run All Tests

# Run tests for all projects
pnpm test

# Run tests for specific project
pnpm --filter @maerchenzauber/backend test
pnpm --filter @memoro/mobile test
pnpm --filter @uload/web test:unit

Adding Tests to NestJS Backend

1. Install Testing Dependencies (if not already installed)

cd apps/YOUR_PROJECT/apps/backend

pnpm add -D @nestjs/testing jest ts-jest @types/jest supertest @types/supertest

2. Create Jest Configuration

Create jest.config.js in your backend directory:

const baseConfig = require('@manacore/test-config/jest-backend');

module.exports = {
	...baseConfig,
	// Project-specific overrides if needed
};

Or inline in package.json:

{
	"jest": {
		"preset": "@manacore/test-config/jest-backend"
	}
}

3. Add Test Scripts to package.json

{
	"scripts": {
		"test": "jest",
		"test:watch": "jest --watch",
		"test:cov": "jest --coverage",
		"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
	}
}

4. Create Your First Test

// src/example/__tests__/example.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { ExampleService } from '../example.service';

describe('ExampleService', () => {
	let service: ExampleService;

	beforeEach(async () => {
		const module: TestingModule = await Test.createTestingModule({
			providers: [ExampleService],
		}).compile();

		service = module.get<ExampleService>(ExampleService);
	});

	it('should be defined', () => {
		expect(service).toBeDefined();
	});
});

5. Run Tests

pnpm test
pnpm test:cov  # With coverage

Adding Tests to React Native Mobile

1. Install Testing Dependencies

cd apps/YOUR_PROJECT/apps/mobile

pnpm add -D jest jest-expo @testing-library/react-native @testing-library/jest-native

2. Create Jest Configuration

Create jest.config.js:

module.exports = {
	preset: '@manacore/test-config/jest-mobile',
	// Project-specific overrides
};

3. Create Setup File

Create jest.setup.js:

import '@testing-library/jest-native/extend-expect';

// Mock Expo modules
jest.mock('expo-secure-store', () => ({
	getItemAsync: jest.fn(),
	setItemAsync: jest.fn(),
	deleteItemAsync: jest.fn(),
}));

jest.mock('expo-font', () => ({
	loadAsync: jest.fn(),
	isLoaded: jest.fn(() => true),
}));

// Global test setup
global.fetch = jest.fn();

4. Add Test Scripts to package.json

{
	"scripts": {
		"test": "jest --watchAll",
		"test:ci": "jest --ci --coverage --watchAll=false",
		"test:cov": "jest --coverage --watchAll=false"
	}
}

5. Create Your First Component Test

// src/components/Button/__tests__/Button.test.tsx
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from '../Button';

describe('Button', () => {
	it('should render', () => {
		const { getByText } = render(<Button>Click Me</Button>);
		expect(getByText('Click Me')).toBeTruthy();
	});

	it('should call onPress', () => {
		const onPress = jest.fn();
		const { getByText } = render(<Button onPress={onPress}>Click</Button>);

		fireEvent.press(getByText('Click'));
		expect(onPress).toHaveBeenCalled();
	});
});

6. Run Tests

pnpm test

Adding Tests to SvelteKit Web

1. Install Testing Dependencies

cd apps/YOUR_PROJECT/apps/web

pnpm add -D vitest @vitest/coverage-v8 @testing-library/svelte jsdom
pnpm add -D @playwright/test  # For E2E tests

2. Create Vitest Configuration

Create vitest.config.ts:

import { defineConfig, mergeConfig } from 'vitest/config';
import svelteConfig from '@manacore/test-config/vitest-svelte';
import { sveltekit } from '@sveltejs/kit/vite';

export default mergeConfig(
	svelteConfig,
	defineConfig({
		plugins: [sveltekit()],
		test: {
			// Project-specific overrides
		},
	})
);

3. Create Vitest Setup File

Create vitest.setup.ts:

import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/svelte';

// Cleanup after each test
afterEach(() => {
	cleanup();
});

4. Create Playwright Configuration (E2E)

Create playwright.config.ts:

import { defineConfig } from '@playwright/test';
import baseConfig from '@manacore/test-config/playwright';

export default defineConfig({
	...baseConfig,
	use: {
		...baseConfig.use,
		baseURL: 'http://localhost:5173',
	},
	webServer: {
		command: 'pnpm run build && pnpm run preview',
		port: 5173,
	},
});

5. Add Test Scripts to package.json

{
	"scripts": {
		"test": "pnpm run test:unit && pnpm run test:e2e",
		"test:unit": "vitest run",
		"test:unit:watch": "vitest",
		"test:unit:cov": "vitest run --coverage",
		"test:e2e": "playwright test",
		"test:e2e:ui": "playwright test --ui"
	}
}

6. Create Your First Component Test

// src/lib/components/Button/__tests__/Button.test.ts
import { render, screen } from '@testing-library/svelte';
import { describe, it, expect, vi } from 'vitest';
import Button from '../Button.svelte';

describe('Button', () => {
	it('should render', () => {
		render(Button, { props: { children: 'Click Me' } });
		expect(screen.getByText('Click Me')).toBeTruthy();
	});

	it('should call onclick', async () => {
		const onclick = vi.fn();
		render(Button, { props: { onclick, children: 'Click' } });

		await screen.getByText('Click').click();
		expect(onclick).toHaveBeenCalled();
	});
});

7. Create Your First E2E Test

// e2e/homepage.spec.ts
import { test, expect } from '@playwright/test';

test('homepage loads successfully', async ({ page }) => {
	await page.goto('/');
	await expect(page.locator('h1')).toBeVisible();
});

8. Run Tests

pnpm test:unit           # Unit tests
pnpm test:e2e            # E2E tests
pnpm test:unit:cov       # With coverage

Adding Tests to Shared Packages

1. Install Vitest

cd packages/YOUR_PACKAGE

pnpm add -D vitest @vitest/coverage-v8

2. Create Vitest Configuration

Create vitest.config.ts:

import { defineConfig, mergeConfig } from 'vitest/config';
import baseConfig from '@manacore/test-config/vitest-base';

export default mergeConfig(
	baseConfig,
	defineConfig({
		test: {
			// Package-specific config
		},
	})
);

3. Add Test Scripts to package.json

{
	"scripts": {
		"test": "vitest run",
		"test:watch": "vitest",
		"test:cov": "vitest run --coverage"
	}
}

4. Create Your First Utility Test

// src/__tests__/format.test.ts
import { describe, it, expect } from 'vitest';
import { formatDate, truncate } from '../format';

describe('formatDate', () => {
	it('should format date correctly', () => {
		const date = new Date('2024-01-15T12:00:00Z');
		expect(formatDate(date, 'yyyy-MM-dd')).toBe('2024-01-15');
	});
});

describe('truncate', () => {
	it('should truncate long strings', () => {
		expect(truncate('Very long text', 10)).toBe('Very long…');
	});
});

5. Run Tests

pnpm test
pnpm test:cov

Running Tests Locally

Individual Project Tests

# Backend
pnpm --filter @maerchenzauber/backend test

# Mobile
pnpm --filter @memoro/mobile test

# Web (unit tests)
pnpm --filter @uload/web test:unit

# Web (E2E tests)
pnpm --filter @uload/web test:e2e

# Shared package
pnpm --filter @manacore/shared-utils test

All Tests for a Project

# Run all tests for maerchenzauber
pnpm --filter maerchenzauber... test

Watch Mode

# Backend (Jest)
pnpm --filter @maerchenzauber/backend test:watch

# Mobile (Jest)
pnpm --filter @memoro/mobile test

# Web (Vitest)
pnpm --filter @uload/web test:unit:watch

With Coverage

# Backend
pnpm --filter @maerchenzauber/backend test:cov

# Mobile
pnpm --filter @memoro/mobile test:cov

# Web
pnpm --filter @uload/web test:unit:cov

# View HTML report
open apps/YOUR_PROJECT/apps/backend/coverage/index.html

Coverage Reports

View Coverage Locally

# Generate coverage
pnpm test:cov

# Open HTML report
open coverage/index.html

Coverage Thresholds

All projects have these default thresholds:

  • Lines: 80%
  • Functions: 80%
  • Branches: 80%
  • Statements: 80%

To override for your project:

Jest (Backend/Mobile):

module.exports = {
	preset: '@manacore/test-config/jest-backend',
	coverageThresholds: {
		global: {
			lines: 90, // Higher threshold
		},
	},
};

Vitest (Web/Shared):

export default defineConfig({
	test: {
		coverage: {
			thresholds: {
				lines: 90,
			},
		},
	},
});

CI/CD Coverage

  • Coverage reports are automatically uploaded to Codecov on PR/push to main
  • Coverage badges available at https://codecov.io/gh/YOUR_ORG/YOUR_REPO
  • PR comments show coverage diff

Troubleshooting

Common Issues

"Cannot find module" errors

# Clear caches
pnpm store prune
pnpm install --force

# Backend: Clear Jest cache
pnpm --filter @YOUR_PROJECT/backend test --clearCache

# Mobile: Clear Metro cache
cd apps/YOUR_PROJECT/apps/mobile
rm -rf node_modules/.cache

Transform errors in React Native

Make sure transformIgnorePatterns in jest.config.js includes all necessary packages:

transformIgnorePatterns: [
	'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@manacore/.*)',
];

Svelte component tests fail

Ensure you have the correct Vite plugin:

import { sveltekit } from '@sveltejs/kit/vite';

export default defineConfig({
	plugins: [sveltekit()],
});

Playwright browser not installed

pnpm --filter @YOUR_PROJECT/web exec playwright install chromium

Tests timeout

Increase timeout in config:

// Vitest
export default defineConfig({
	test: {
		testTimeout: 30000, // 30 seconds
	},
});

// Jest
module.exports = {
	testTimeout: 30000,
};

Coverage not generating

# Jest: Ensure collectCoverageFrom is set
collectCoverageFrom: [
  'src/**/*.{ts,tsx}',
  '!**/*.d.ts',
],

# Vitest: Ensure include is set
coverage: {
  include: ['src/**/*.{js,ts,svelte}'],
}

Getting Help

  1. Check existing tests in the project for patterns
  2. Review docs/TESTING.md for detailed strategies
  3. Check example tests in docs/test-examples/
  4. Review CI logs for failure details
  5. Ask in team chat for project-specific guidance

Next Steps

  1. Start with critical paths: Auth, payments, data integrity
  2. Add tests incrementally: Don't try to test everything at once
  3. Follow TDD when possible: Write tests before code
  4. Review coverage: Aim for 80% minimum, 100% for critical code
  5. Keep tests fast: Unit tests < 100ms, integration < 1s
  6. Update this guide: Add project-specific tips as you learn

Resources


Quick Reference Commands

# Run all tests
pnpm test

# Run specific project tests
pnpm --filter @PROJECT/APP test

# Run with coverage
pnpm --filter @PROJECT/APP test:cov

# Run in watch mode
pnpm --filter @PROJECT/APP test:watch

# Run E2E tests
pnpm --filter @PROJECT/web test:e2e

# Type check
pnpm type-check

# Lint
pnpm lint

# Format
pnpm format