testing/jest-testing-skill/SKILL.md
Set up Jest 30+ with TypeScript for unit tests, integration tests, mocking (jest.fn, jest.mock, jest.spyOn), coverage configuration, custom matchers, snapshot testing, and setup/teardown patterns.
npx skillsauth add achreftlili/deep-dev-skills jest-testing-skillInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Set up Jest 30+ with TypeScript for unit tests, integration tests, mocking (jest.fn, jest.mock, jest.spyOn), coverage configuration, custom matchers, snapshot testing, and setup/teardown patterns.
npm install -D jest @jest/globals ts-jest @types/jest
# Initialize Jest config
npx ts-jest config:init
# For ESM projects, use jest with SWC for faster transforms
npm install -D @swc/core @swc/jest
src/
modules/
users/
users.service.ts
users.service.spec.ts # Unit test — co-located with source
users.repository.ts
__mocks__/ # Manual mocks for this module
users.repository.ts
test/
setup.ts # Global test setup
helpers/
factories.ts # Test data factories
matchers.ts # Custom matchers
integration/
users.integration.spec.ts # Integration tests
jest.config.ts # Jest configuration
.spec.ts suffix.test/ directory.describe block per function/method under test. Nest describe for different scenarios.jest.fn() for simple stubs, jest.mock() for module-level mocking, jest.spyOn() for partial mocking.jest.clearAllMocks() in beforeEach or restoreMocks: true in config.jest.config.ts)import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/src", "<rootDir>/test"],
testMatch: ["**/*.spec.ts", "**/*.test.ts"],
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
setupFilesAfterSetup: ["<rootDir>/test/setup.ts"],
collectCoverageFrom: [
"src/**/*.ts",
"!src/**/*.spec.ts",
"!src/**/*.d.ts",
"!src/**/index.ts",
],
coverageThresholds: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
clearMocks: true,
restoreMocks: true,
};
export default config;
import type { Config } from "jest";
const config: Config = {
testEnvironment: "node",
roots: ["<rootDir>/src", "<rootDir>/test"],
testMatch: ["**/*.spec.ts", "**/*.test.ts"],
transform: {
"^.+\\.ts$": ["@swc/jest"],
},
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
clearMocks: true,
restoreMocks: true,
};
export default config;
test/setup.ts)// Extend expect with custom matchers (optional)
import "./helpers/matchers";
// Set test timeout
jest.setTimeout(10000);
// Suppress console.log in tests (optional)
beforeAll(() => {
jest.spyOn(console, "log").mockImplementation(() => {});
});
afterAll(() => {
jest.restoreAllMocks();
});
users.service.spec.ts)import { describe, it, expect, jest, beforeEach } from "@jest/globals";
import { UsersService } from "./users.service";
import { UsersRepository } from "./users.repository";
// Mock the repository module
jest.mock("./users.repository");
describe("UsersService", () => {
let service: UsersService;
let mockRepo: jest.Mocked<UsersRepository>;
beforeEach(() => {
mockRepo = new UsersRepository() as jest.Mocked<UsersRepository>;
service = new UsersService(mockRepo);
});
describe("findById", () => {
it("should return the user when found", async () => {
const mockUser = { id: "1", email: "[email protected]", name: "Test User" };
mockRepo.findById.mockResolvedValue(mockUser);
const result = await service.findById("1");
expect(result).toEqual(mockUser);
expect(mockRepo.findById).toHaveBeenCalledWith("1");
expect(mockRepo.findById).toHaveBeenCalledTimes(1);
});
it("should throw NotFoundException when user not found", async () => {
mockRepo.findById.mockResolvedValue(null);
await expect(service.findById("999")).rejects.toThrow("User not found");
});
});
describe("create", () => {
it("should hash the password before saving", async () => {
const input = { email: "[email protected]", name: "New User", password: "plain123" };
const saved = { id: "2", ...input, password: "hashed" };
mockRepo.create.mockResolvedValue(saved);
const result = await service.create(input);
expect(result.id).toBe("2");
// Verify password was not stored as plain text
expect(mockRepo.create).toHaveBeenCalledWith(
expect.objectContaining({
email: "[email protected]",
password: expect.not.stringContaining("plain123"),
})
);
});
});
});
import { describe, it, expect, jest } from "@jest/globals";
import * as emailService from "../services/email.service";
import { notifyUser } from "./notification.service";
describe("notifyUser", () => {
it("should send an email with the correct subject", async () => {
const sendSpy = jest
.spyOn(emailService, "sendEmail")
.mockResolvedValue({ messageId: "abc" });
await notifyUser("[email protected]", "Welcome!");
expect(sendSpy).toHaveBeenCalledWith({
to: "[email protected]",
subject: "Welcome!",
body: expect.stringContaining("Welcome"),
});
});
});
describe("processItems", () => {
it("should call the callback for each item", () => {
const callback = jest.fn<(item: string) => void>();
const items = ["a", "b", "c"];
processItems(items, callback);
expect(callback).toHaveBeenCalledTimes(3);
expect(callback).toHaveBeenNthCalledWith(1, "a");
expect(callback).toHaveBeenNthCalledWith(2, "b");
expect(callback).toHaveBeenNthCalledWith(3, "c");
});
});
test/helpers/factories.ts)interface User {
id: string;
email: string;
name: string;
role: string;
createdAt: Date;
}
let counter = 0;
export function buildUser(overrides: Partial<User> = {}): User {
counter++;
return {
id: `user-${counter}`,
email: `user${counter}@example.com`,
name: `Test User ${counter}`,
role: "user",
createdAt: new Date("2024-01-01"),
...overrides,
};
}
test/helpers/matchers.ts)expect.extend({
toBeWithinRange(received: number, floor: number, ceiling: number) {
const pass = received >= floor && received <= ceiling;
return {
pass,
message: () =>
`expected ${received} ${pass ? "not " : ""}to be within range ${floor} - ${ceiling}`,
};
},
});
declare global {
namespace jest {
interface Matchers<R> {
toBeWithinRange(floor: number, ceiling: number): R;
}
}
}
import { describe, it, expect } from "@jest/globals";
import { formatUserResponse } from "./formatters";
describe("formatUserResponse", () => {
it("should match the expected output shape", () => {
const user = {
id: "1",
email: "[email protected]",
name: "Test User",
createdAt: new Date("2024-01-01T00:00:00Z"),
};
expect(formatUserResponse(user)).toMatchSnapshot();
});
// Inline snapshot — no separate file
it("should format the date correctly", () => {
const result = formatDate(new Date("2024-06-15T12:00:00Z"));
expect(result).toMatchInlineSnapshot(`"June 15, 2024"`);
});
});
describe("async operations", () => {
it("should reject with a specific error", async () => {
await expect(fetchData("invalid")).rejects.toThrow("Not found");
});
it("should reject with an error matching properties", async () => {
await expect(fetchData("invalid")).rejects.toMatchObject({
statusCode: 404,
message: expect.stringContaining("not found"),
});
});
});
describe("debounce", () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it("should call the function after the delay", () => {
const fn = jest.fn();
const debounced = debounce(fn, 300);
debounced();
expect(fn).not.toHaveBeenCalled();
jest.advanceTimersByTime(300);
expect(fn).toHaveBeenCalledTimes(1);
});
});
// tests/mocks/handlers.ts
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("/api/users", () => {
return HttpResponse.json([
{ id: "1", name: "Alice", email: "[email protected]" },
]);
}),
http.post("/api/users", async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: "2", ...body }, { status: 201 });
}),
];
// tests/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
// tests/setup.ts — add MSW lifecycle hooks
import { server } from "./mocks/server";
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// tests/integration/users.integration.spec.ts
import { describe, it, expect } from "@jest/globals";
import { http, HttpResponse } from "msw";
import { server } from "../mocks/server";
import { fetchUsers } from "../../src/modules/users/users.service";
describe("Users API integration", () => {
it("should fetch users from the API", async () => {
const users = await fetchUsers();
expect(users).toHaveLength(1);
expect(users[0]).toMatchObject({ id: "1", name: "Alice" });
});
it("should handle API errors", async () => {
server.use(
http.get("/api/users", () => {
return HttpResponse.json({ message: "Server error" }, { status: 500 });
})
);
await expect(fetchUsers()).rejects.toThrow();
});
});
# Run all tests
npx jest
# Run tests in watch mode
npx jest --watch
# Run a specific test file
npx jest src/modules/users/users.service.spec.ts
# Run tests matching a pattern
npx jest --testNamePattern="should return the user"
# Run with coverage
npx jest --coverage
# Update snapshots
npx jest --updateSnapshot
# Run only changed files (CI-friendly)
npx jest --changedSince=main
# Verbose output
npx jest --verbose
# Debug a test
node --inspect-brk node_modules/.bin/jest --runInBand src/modules/users/users.service.spec.ts
@nestjs/testing Test.createTestingModule with Jest. Mock providers using .overrideProvider().useValue().supertest for HTTP-level integration tests: npm install -D supertest @types/supertest.github-actions-ci skill. Run npx jest --coverage --ci in the CI workflow. Upload coverage artifacts.vitest-testing-skill.# docker-compose.test.yml
services:
test-db:
image: postgres:16-alpine
environment:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- "5433:5432"
tmpfs:
- /var/lib/postgresql/data # RAM-backed for speed
Use DATABASE_URL=postgresql://test:test@localhost:5433/testdb in your test environment. Start with docker compose -f docker-compose.test.yml up -d before running integration tests.
testing
Set up Vitest 2.x with TypeScript for unit and component testing using test/describe/it, vi.fn/vi.mock/vi.spyOn, component testing with Testing Library, coverage (v8/istanbul), workspace config, and snapshot testing.
testing
Set up pytest 8.x with Python for unit and integration testing using fixtures (scope, autouse, parametrize), async tests (pytest-asyncio), mocking (unittest.mock, pytest-mock), coverage (pytest-cov), conftest.py patterns, and markers.
testing
Set up Playwright 1.49+ with TypeScript for E2E testing using page object model, fixtures, test.describe/test blocks, assertions, selectors, network mocking, CI configuration, and trace viewer.
tools
--- name: react-native-project-starter description: > Scaffold a production-ready React Native 0.76+ app with Expo SDK 52+, Expo Router for file-based navigation, Zustand for state management, TypeScript, EAS Build, platform-specific code, and AsyncStorage. category: mobile agent-type: coding compatibility: Node.js >= 20.x, npm >= 10.x, Expo CLI, EAS CLI, iOS: Xcode 16+, Android: Android Studio with SDK 35+ --- # React Native Project Starter > Scaffold a production-ready React Native 0.7