Testing
Testing
Manacore uses Jest for backend testing and Vitest for frontend testing.
Quick Start
# Run all testspnpm test
# Run tests for specific projectpnpm --filter @chat/backend test
# Run tests in watch modepnpm --filter @chat/backend test:watch
# Run with coveragepnpm --filter @chat/backend test:covTest Structure
Tests are colocated with source files:
src/├── users/│ ├── users.service.ts│ ├── users.service.spec.ts # Unit test│ ├── users.controller.ts│ └── users.controller.spec.ts└── __tests__/ └── users.e2e.spec.ts # E2E testUnit Testing
NestJS Services
import { Test, TestingModule } from '@nestjs/testing';import { UsersService } from './users.service';import { DRIZZLE } from '@manacore/shared-drizzle';
describe('UsersService', () => { let service: UsersService; let mockDb: any;
beforeEach(async () => { mockDb = { select: jest.fn().mockReturnThis(), from: jest.fn().mockReturnThis(), where: jest.fn().mockResolvedValue([{ id: '1', email: 'test@example.com' }]), insert: jest.fn().mockReturnThis(), values: jest.fn().mockReturnThis(), returning: jest.fn().mockResolvedValue([{ id: '1', email: 'test@example.com' }]), };
const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, { provide: DRIZZLE, useValue: mockDb }, ], }).compile();
service = module.get<UsersService>(UsersService); });
it('should find user by id', async () => { const user = await service.findById('1'); expect(user).toEqual({ id: '1', email: 'test@example.com' }); });
it('should create user', async () => { const user = await service.create({ email: 'new@example.com', name: 'Test' }); expect(mockDb.insert).toHaveBeenCalled(); expect(user.email).toBe('test@example.com'); });});NestJS Controllers
import { Test, TestingModule } from '@nestjs/testing';import { UsersController } from './users.controller';import { UsersService } from './users.service';
describe('UsersController', () => { let controller: UsersController; let mockService: Partial<UsersService>;
beforeEach(async () => { mockService = { findById: jest.fn().mockResolvedValue({ id: '1', email: 'test@example.com' }), create: jest.fn().mockResolvedValue({ id: '2', email: 'new@example.com' }), };
const module: TestingModule = await Test.createTestingModule({ controllers: [UsersController], providers: [{ provide: UsersService, useValue: mockService }], }).compile();
controller = module.get<UsersController>(UsersController); });
it('should get user', async () => { const user = await controller.getUser('1'); expect(user.email).toBe('test@example.com'); expect(mockService.findById).toHaveBeenCalledWith('1'); });});Frontend Testing
SvelteKit Components (Vitest)
import { render, screen } from '@testing-library/svelte';import { describe, it, expect } from 'vitest';import Button from './Button.svelte';
describe('Button', () => { it('renders with text', () => { render(Button, { props: { label: 'Click me' } }); expect(screen.getByText('Click me')).toBeInTheDocument(); });
it('handles click', async () => { const { component } = render(Button, { props: { label: 'Click' } }); let clicked = false; component.$on('click', () => { clicked = true; });
await screen.getByText('Click').click(); expect(clicked).toBe(true); });});React Native (Jest)
import { render, screen, fireEvent } from '@testing-library/react-native';import { Button } from './Button';
describe('Button', () => { it('renders correctly', () => { render(<Button title="Press me" onPress={() => {}} />); expect(screen.getByText('Press me')).toBeTruthy(); });
it('calls onPress when pressed', () => { const onPress = jest.fn(); render(<Button title="Press" onPress={onPress} />);
fireEvent.press(screen.getByText('Press')); expect(onPress).toHaveBeenCalled(); });});E2E Testing
NestJS with Supertest
import { Test, TestingModule } from '@nestjs/testing';import { INestApplication } from '@nestjs/common';import * as request from 'supertest';import { AppModule } from '../src/app.module';
describe('Users (e2e)', () => { let app: INestApplication;
beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile();
app = moduleFixture.createNestApplication(); await app.init(); });
afterAll(async () => { await app.close(); });
it('/users (GET)', () => { return request(app.getHttpServer()) .get('/users') .expect(200) .expect((res) => { expect(Array.isArray(res.body)).toBe(true); }); });
it('/users (POST)', () => { return request(app.getHttpServer()) .post('/users') .send({ email: 'test@example.com', name: 'Test User' }) .expect(201) .expect((res) => { expect(res.body.email).toBe('test@example.com'); }); });});Mock Factories
Create reusable mock factories:
export const createMockUser = (overrides = {}) => ({ id: 'user-123', email: 'test@example.com', name: 'Test User', createdAt: new Date(), ...overrides,});
// Usageconst user = createMockUser({ name: 'Custom Name' });Test Configuration
Jest Config (Backend)
module.exports = { moduleFileExtensions: ['js', 'json', 'ts'], rootDir: 'src', testRegex: '.*\\.spec\\.ts$', transform: { '^.+\\.(t|j)s$': 'ts-jest', }, collectCoverageFrom: ['**/*.(t|j)s'], coverageDirectory: '../coverage', testEnvironment: 'node',};Vitest Config (Frontend)
import { defineConfig } from 'vitest/config';import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({ plugins: [svelte({ hot: !process.env.VITEST })], test: { include: ['src/**/*.{test,spec}.{js,ts}'], environment: 'jsdom', globals: true, },});Best Practices
Coverage Goals
- Unit tests: 80% coverage for business logic
- Integration tests: Critical paths and API endpoints
- E2E tests: Happy path user flows