Skip to content

Testing

Testing

Manacore uses Jest for backend testing and Vitest for frontend testing.

Quick Start

Terminal window
# Run all tests
pnpm test
# Run tests for specific project
pnpm --filter @chat/backend test
# Run tests in watch mode
pnpm --filter @chat/backend test:watch
# Run with coverage
pnpm --filter @chat/backend test:cov

Test 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 test

Unit 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:

test/factories/user.factory.ts
export const createMockUser = (overrides = {}) => ({
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
createdAt: new Date(),
...overrides,
});
// Usage
const user = createMockUser({ name: 'Custom Name' });

Test Configuration

Jest Config (Backend)

jest.config.js
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)

vitest.config.ts
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