Testing & CI/CD Skill
This skill covers testing strategies, CI/CD pipelines, and development workflows for the SE104_VLEAGUE project.
Testing Overview
The project uses different testing strategies for API and Web:
- •API (apps/api): Jest for unit tests + Supertest for E2E tests
- •Web (apps/web): ESLint for code quality
- •All workspaces: Prettier for code formatting
API Testing
Test Structure
apps/api/
├── src/
│ └── **/*.spec.ts # Unit tests (co-located with source)
└── test/
└── **/*.e2e-spec.ts # E2E tests
Running Tests
cd apps/api # Run all unit tests pnpm test # Run tests in watch mode pnpm test:watch # Generate coverage report pnpm test:cov # Run E2E tests pnpm test:e2e # Run specific test file pnpm test -- teams.service.spec.ts
Unit Test Example
// src/registration/teams.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { TeamsService } from './teams.service';
import { PrismaService } from '../prisma/prisma.service';
describe('TeamsService', () => {
let service: TeamsService;
let prisma: PrismaService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TeamsService,
{
provide: PrismaService,
useValue: {
team: {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
},
},
],
}).compile();
service = module.get<TeamsService>(TeamsService);
prisma = module.get<PrismaService>(PrismaService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('findAll', () => {
it('should return an array of teams', async () => {
const mockTeams = [
{ id: '1', name: 'Team A', status: 'ACTIVE' },
{ id: '2', name: 'Team B', status: 'ACTIVE' },
];
jest.spyOn(prisma.team, 'findMany').mockResolvedValue(mockTeams);
const result = await service.findAll();
expect(result).toEqual(mockTeams);
expect(prisma.team.findMany).toHaveBeenCalled();
});
});
describe('create', () => {
it('should create a new team', async () => {
const createDto = { name: 'New Team', status: 'ACTIVE' };
const mockTeam = { id: '1', ...createDto };
jest.spyOn(prisma.team, 'create').mockResolvedValue(mockTeam);
const result = await service.create(createDto);
expect(result).toEqual(mockTeam);
expect(prisma.team.create).toHaveBeenCalledWith({ data: createDto });
});
});
});
E2E Test Example
// test/teams.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Teams API (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('/teams (GET) - should return list of teams', () => {
return request(app.getHttpServer())
.get('/teams')
.expect(200)
.expect((res) => {
expect(Array.isArray(res.body)).toBe(true);
});
});
it('/teams (POST) - should create a new team', () => {
const createDto = {
name: 'Test Team',
status: 'ACTIVE',
};
return request(app.getHttpServer())
.post('/teams')
.send(createDto)
.expect(201)
.expect((res) => {
expect(res.body).toHaveProperty('id');
expect(res.body.name).toBe(createDto.name);
});
});
});
Test Configuration
Jest configuration is in apps/api/package.json:
{
"jest": {
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": ["**/*.(t|j)s"],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
Linting and Formatting
Running Linters
# Root level (all workspaces) pnpm lint # Run ESLint on all workspaces pnpm format # Run Prettier on all files # API only cd apps/api pnpm lint # ESLint with auto-fix # Web only cd apps/web pnpm lint # ESLint
ESLint Configuration
API: apps/api/eslint.config.mjs
Web: apps/web/eslint.config.js
Both use TypeScript ESLint with project-specific rules.
Prettier Configuration
Root level: .prettierrc.cjs
module.exports = {
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 80,
tabWidth: 2,
};
CI/CD Pipeline
GitHub Actions Workflows
The project has multiple CI/CD workflows in .github/workflows/:
| Workflow | File | Purpose |
|---|---|---|
| CI | ci.yml | Main CI pipeline (lint, test, build) |
| PR Labeler | pr-labeler.yml | Auto-label PRs based on title, files, size |
| CodeQL | codeql.yml | Security analysis for JS/TS |
CI Pipeline Features
The main CI pipeline (ci.yml) includes:
- •Concurrency Control: Cancels in-progress runs when new commits are pushed
- •pnpm Caching: Speeds up dependency installation
- •TypeScript Type Checking: Runs
tsc --noEmitbefore build - •Security Audit: Checks for vulnerable dependencies
- •Test Coverage: Uploads coverage reports to Codecov
- •Build Artifacts: Uploads build outputs for deployment
CI Pipeline Jobs
┌─────────────────────────────────────────────────────────────┐ │ CI Pipeline │ ├─────────────────────────────────────────────────────────────┤ │ api │ web │ security │ │ ├─ Lint │ ├─ Lint │ └─ pnpm audit │ │ ├─ Type Check │ ├─ Type Check │ │ │ ├─ Test + Coverage │ └─ Build │ │ │ └─ Build │ │ │ ├─────────────────────────────────────────────────────────────┤ │ pr-title │ pr-branch │ pr-size │ │ (Conventional) │ (Naming) │ (Warnings) │ ├─────────────────────────────────────────────────────────────┤ │ ci-success │ │ (Final status gate) │ └─────────────────────────────────────────────────────────────┘
Running CI Locally
Simulate what CI runs locally:
# Full CI simulation pnpm lint && pnpm test && pnpm build # API only cd apps/api pnpm lint pnpm exec tsc --noEmit pnpm test pnpm test:cov pnpm build # Web only cd apps/web pnpm lint pnpm exec tsc --noEmit pnpm build # Security audit pnpm audit --audit-level high
Environment Variables in CI
CI uses these environment variables:
| Variable | Purpose |
|---|---|
NODE_VERSION | Node.js version (20) |
STORE_PATH | pnpm store directory for caching |
Dependabot Configuration
The project uses Dependabot (.github/dependabot.yml) for automatic dependency updates:
- •NPM dependencies: Weekly updates on Monday
- •GitHub Actions: Monthly updates
- •Grouping: Minor/patch updates are grouped to reduce PR noise
- •Labels: Auto-labeled with
dependencies,type:chore, and area labels
CodeQL Security Analysis
The CodeQL workflow (.github/workflows/codeql.yml) provides:
- •JavaScript/TypeScript security scanning
- •Runs on push, PRs, and weekly schedule
- •Reports vulnerabilities in GitHub Security tab
PR Auto-Labeling
The PR labeler workflow adds labels based on:
- •
PR Title (Conventional Commits):
- •
feat:→type:feature - •
fix:→type:bugfix - •
chore:→type:chore - •etc.
- •
- •
Changed Files:
- •
apps/api/*→area:api - •
apps/web/*→area:web - •
.github/*→area:ci - •
*.md→area:docs
- •
- •
PR Size:
- •
size:XS(<10 changes) - •
size:S(10-49 changes) - •
size:M(50-199 changes) - •
size:L(200-499 changes) - •
size:XL(500+ changes)
- •
Conventional Commits
PR Title Format
All PRs to main must follow Conventional Commits format:
<type>: <summary>
Allowed types:
- •
feat: New feature - •
fix: Bug fix - •
chore: Maintenance tasks - •
docs: Documentation changes - •
refactor: Code refactoring - •
test: Adding or updating tests - •
ci: CI/CD changes
Examples:
- •✅
feat: add team registration endpoint - •✅
fix: resolve match scheduling conflict - •✅
chore: update dependencies - •✅
docs: update README with setup instructions - •❌
Add new feature(missing type) - •❌
Feature: add teams(wrong capitalization)
PR Title Validation
The CI pipeline includes a PR title check that validates the format using a GitHub Action.
Branch Protection
Rules for main branch:
- •Require Pull Request: Direct pushes to
mainare blocked - •Require Approvals: At least 1 approval needed
- •Required Status Checks:
- •API lint must pass
- •API test must pass
- •API build must pass
- •Web lint must pass
- •Web build must pass
- •PR title check must pass
- •Up-to-date branches: Branch must be up-to-date with
main
Development Workflow
Standard Workflow
- •
Create Feature Branch:
bashgit checkout -b feat/add-player-management
- •
Make Changes:
- •Write code
- •Write tests
- •Run tests locally
- •
Lint and Format:
bashpnpm lint pnpm format
- •
Commit Changes:
bashgit add . git commit -m "feat: add player management endpoints"
- •
Push and Create PR:
bashgit push origin feat/add-player-management
- •Create PR with Conventional Commits title
- •Wait for CI to pass
- •Request review
- •
Merge:
- •Once approved and CI passes
- •Squash and merge to
main
Pre-commit Checklist
Before pushing code, ensure:
- • Code compiles without errors
- • All tests pass (
pnpm test) - • No linting errors (
pnpm lint) - • Code is formatted (
pnpm format) - • New features have tests
- • Database migrations are created if schema changed
- • Environment variables documented if added
CI/CD Best Practices
[!TIP] Test Locally First: Always run
pnpm lintandpnpm testlocally before pushing to avoid CI failures.
[!TIP] Small PRs: Keep PRs small and focused. Easier to review and faster to merge.
[!IMPORTANT] Prisma Generate: The project has a
postinstallscript that runsprisma generate. This ensures CI has the Prisma Client available.
[!WARNING] Environment Variables: Never commit
.envfiles or secrets to Git. Use GitHub Secrets for sensitive data in CI.
Troubleshooting CI Failures
API Build Fails
Cause: Prisma Client not generated
Solution: The postinstall script should handle this, but if it fails:
- name: Generate Prisma Client working-directory: apps/api run: pnpm dlx prisma generate
Tests Fail in CI but Pass Locally
Causes:
- •Missing environment variables
- •Database connection issues
- •Different Node.js versions
Solutions:
- •Check
DATABASE_URLis set in workflow - •Verify PostgreSQL service is running
- •Match Node.js version (20.x) in workflow
Lint Errors
Cause: Code doesn't follow ESLint rules
Solution:
cd apps/api # or apps/web pnpm lint # Shows errors
Fix manually or use auto-fix where possible.
PR Title Check Fails
Cause: PR title doesn't follow Conventional Commits
Solution: Edit PR title to match format:
feat: description
Common Commands Summary
# Testing pnpm test # Run all tests (root) pnpm --filter api test # API tests only pnpm test:watch # Watch mode pnpm test:cov # Coverage report # Linting & Formatting pnpm lint # Lint all workspaces pnpm format # Format all files pnpm --filter api lint # API only pnpm --filter web lint # Web only # Building pnpm build # Build all workspaces pnpm --filter api build pnpm --filter web build # CI Simulation (run what CI runs) pnpm lint && pnpm test && pnpm build
Build Artifacts
After successful CI runs, the following artifacts are produced:
- •API:
apps/api/dist/- Compiled JavaScript - •Web:
apps/web/dist/- Optimized static files
[!NOTE] Currently, artifacts are built but not deployed. Deployment configuration may be added in the future.
Monitoring CI
View CI Runs
- •Go to GitHub repository
- •Click "Actions" tab
- •View workflow runs
CI Status Badge
Add to README.md:

Future CI/CD Enhancements
Potential additions:
- • Automated deployment to staging
- • E2E tests with Playwright/Cypress
- • Performance testing
- • Security scanning
- • Dependency vulnerability checks
- • Docker image publishing
- • Automated releases with semantic versioning