Testing
True Life uses Jest for comprehensive testing across all runtimes. The testing framework supports unit tests, server/client tests with FiveM mocks, simulated in-game tests, full E2E scenarios, and React UI tests.
Overview
| Test Type | File Pattern | Command | Purpose | Scope |
|---|---|---|---|---|
| Unit | *.test.ts, *.spec.ts | pnpm test:unit | Pure logic, no FiveM context | Single function/class |
| Server | *.server.test.ts | pnpm test:server | Server-side unit tests with mocked natives | Server runtime in isolation |
| Client | *.client.test.ts | pnpm test:client | Client-side unit tests with mocked natives | Client runtime in isolation |
| Simulated | *.simulated.test.ts | pnpm test:simulated | Integration tests across client/server/web | Cross-runtime flows (CI/CD) |
| E2E | *.e2e.test.ts | pnpm test:e2e | Full system scenarios with multi-client sim | Complex user journeys |
| Web | *.test.tsx, *.spec.tsx | pnpm test:web | React component tests with jsdom | UI components |
When to Use Each
- Unit: Pure functions, utilities, type transformations, algorithms
- Server/Client: Runtime-specific behavior in isolation (observables, natives, entity wrappers)
- Simulated: RPC flows, cross-runtime state sync, service interactions — runs in CI/CD
- E2E: Multi-player scenarios, event timelines, complex user journeys
- Web: React components, hooks, UI state, DOM interactions
Quick Start
# Run all tests
pnpm test
# Run specific test type
pnpm test:unit
pnpm test:server
pnpm test:client
pnpm test:simulated
pnpm test:e2e
pnpm test:web
# Watch mode
pnpm test:watch
pnpm test:unit:watch
# Coverage
pnpm test:coverage
# CI mode (optimized for CI environments)
pnpm test:ciDirectory Structure
test/
├── setup/ # Jest setup files
│ ├── unit.setup.ts # Unit test environment
│ ├── server.setup.ts # Server environment with mocks
│ ├── client.setup.ts # Client environment with mocks
│ ├── simulated.setup.ts # Simulated RPC test setup
│ ├── e2e.setup.ts # E2E test setup
│ └── web.setup.ts # Web/UI test setup
├── mocks/ # Mock implementations
│ ├── fivem/
│ │ └── natives.ts # FiveM native function mocks
│ ├── rpc/
│ │ └── mock-rpc.ts # RPC system mock
│ └── di/
│ └── mock-di.ts # Dependency injection mock
├── harness/ # Test harnesses
│ ├── simulated-harness.ts # Client/server simulation
│ └── e2e-harness.ts # Multi-client E2E
├── utils/ # Test utilities
│ └── react-testing.ts # React testing helpers
├── examples/ # Example tests
│ ├── unit.example.test.ts
│ ├── simulated.example.test.ts
│ └── e2e.example.test.ts
├── index.ts # Main exports
└── README.md # Quick referenceUnit Tests
For testing pure logic without FiveM dependencies.
Basic Example
// src/modules/my-module/utils.test.ts
import { describe, it, expect } from "@jest/globals";
import { calculateTax, formatCurrency } from "./utils";
describe("calculateTax", () => {
it("should calculate 10% tax", () => {
expect(calculateTax(100, 0.1)).toBe(10);
});
it("should handle zero amount", () => {
expect(calculateTax(0, 0.1)).toBe(0);
});
});
describe("formatCurrency", () => {
it("should format with dollar sign", () => {
expect(formatCurrency(1234.56)).toBe("$1,234.56");
});
});Testing with Mocks
import { describe, it, expect, jest, beforeEach } from "@jest/globals";
import { NotificationManager } from "./notification-manager";
describe("NotificationManager", () => {
let manager: NotificationManager;
beforeEach(() => {
manager = new NotificationManager();
jest.useFakeTimers();
});
it("should auto-dismiss after duration", () => {
manager.show({ message: "Test", duration: 5000 });
expect(manager.getNotifications()).toHaveLength(1);
jest.advanceTimersByTime(5000);
expect(manager.getNotifications()).toHaveLength(0);
});
});Server Tests (Runtime Unit)
For testing server-side logic in isolation with FiveM native mocks. Use these for testing repositories, server observables, player state management, and database operations without needing cross-runtime communication.
Basic Server Test
// src/modules/my-module/my.service.server.test.ts
import { describe, it, expect, beforeEach } from "@jest/globals";
import { createServiceInstance } from "@test/mocks/di/mock-di";
import { addMockPlayer } from "@test/mocks/fivem/natives";
import { MyService } from "./my.service";
describe("MyService (server)", () => {
let service: MyService;
beforeEach(() => {
// Create service with DI support
service = createServiceInstance(MyService);
// Add mock players
addMockPlayer(1, "TestPlayer", ["license:test123", "discord:123456"]);
});
it("should get player data", async () => {
const result = await service.getPlayerData({
source: 1,
args: ["test-id"],
});
expect(result).toBeDefined();
expect(result.playerId).toBe(1);
});
it("should validate player permissions", async () => {
const hasPermission = await service.checkPermission({
source: 1,
args: ["admin"],
});
expect(hasPermission).toBe(false);
});
});Testing with Database Mocks
import { describe, it, expect, beforeEach, jest } from "@jest/globals";
import { createServiceInstance, registerMockService } from "@test/mocks/di/mock-di";
import { BankingService } from "./banking.service";
import { BankingRepository } from "./repository";
// Create mock repository
const mockRepository = {
findAccount: jest.fn(),
updateBalance: jest.fn(),
};
describe("BankingService (server)", () => {
let service: BankingService;
beforeEach(() => {
// Register mock repository
registerMockService(BankingRepository, mockRepository);
// Create service (will use mock repository via DI)
service = createServiceInstance(BankingService);
// Reset mocks
jest.clearAllMocks();
});
it("should transfer funds between accounts", async () => {
mockRepository.findAccount.mockResolvedValue({ id: "1", balance: 1000 });
mockRepository.updateBalance.mockResolvedValue(true);
const result = await service.transfer({
source: 1,
args: ["account-1", "account-2", 100],
});
expect(result.success).toBe(true);
expect(mockRepository.updateBalance).toHaveBeenCalledTimes(2);
});
});Client Tests (Runtime Unit)
For testing client-side logic in isolation with FiveM native mocks. Use these for testing entity wrappers, client observables, native calls, and input handling without needing cross-runtime communication.
Basic Client Test
// src/modules/my-module/my.service.client.test.ts
import { describe, it, expect, beforeEach } from "@jest/globals";
import { createServiceInstance } from "@test/mocks/di/mock-di";
import { setCurrentPlayerId, setCurrentPedId, setMockPedHealth } from "@test/mocks/fivem/natives";
import { PlayerService } from "./player.service";
describe("PlayerService (client)", () => {
let service: PlayerService;
beforeEach(() => {
service = createServiceInstance(PlayerService);
// Setup client state
setCurrentPlayerId(1);
setCurrentPedId(1000);
setMockPedHealth(1000, 150, 200);
});
it("should get current player health", () => {
const health = service.getCurrentHealth();
expect(health).toBe(150);
});
it("should detect low health state", () => {
setMockPedHealth(1000, 25, 200);
const isLowHealth = service.isLowHealth();
expect(isLowHealth).toBe(true);
});
});Testing NUI Communication
import { describe, it, expect, beforeEach } from "@jest/globals";
import { createServiceInstance } from "@test/mocks/di/mock-di";
import { getNuiMessageQueue, clearNuiMessageQueue } from "@test/mocks/fivem/natives";
import { HudService } from "./hud.service";
describe("HudService (client)", () => {
let service: HudService;
beforeEach(() => {
service = createServiceInstance(HudService);
clearNuiMessageQueue();
});
it("should send NUI message when updating HUD", () => {
service.updateHudState({ visible: true, data: "test" });
const messages = getNuiMessageQueue();
expect(messages).toHaveLength(1);
expect(messages[0].type).toBe("hud:update");
expect(messages[0].data).toMatchObject({ visible: true });
});
});Simulated Tests (Integration)
For testing cross-runtime flows (RPC communication, observable sync, service interactions) in a fully mocked environment. These tests simulate all three runtime environments (client/server/web) in a single process, making them ideal for CI/CD pipelines.
Basic Simulated Test
// test/simulated/my-feature.simulated.test.ts
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { createSimulatedHarness, type SimulatedHarness } from "@test/harness/simulated-harness";
import { MyService } from "@modules/my-module/my.service";
describe("MyFeature (simulated)", () => {
let harness: SimulatedHarness;
beforeEach(() => {
harness = createSimulatedHarness();
});
afterEach(() => {
harness.dispose();
});
it("should handle client-to-server RPC", async () => {
// Create services on server and client
const serverService = harness.server.createService(MyService);
const client = harness.createClient(1);
const clientService = client.createService(MyService);
// Simulate player joining
await harness.server.triggerPlayerJoining(1);
// Make RPC call from client to server
const result = await clientService.fetchData({
source: 0, // 0 = server
args: ["test-id"],
});
// Verify result
expect(result).toBeDefined();
// Verify RPC was called
harness.expectRpcCalled("MyService:fetchData");
});
it("should handle server-to-client RPC", async () => {
const serverService = harness.server.createService(MyService);
const client = harness.createClient(1);
client.createService(MyService);
await harness.server.triggerPlayerJoining(1);
// Server sends notification to client
await serverService.notifyPlayer({
source: 1, // Target player ID
args: ["Hello!"],
noResponse: true,
});
// Verify RPC was called
harness.expectRpcCalled("MyService:notifyPlayer");
});
});Testing Multiple Players
describe("Multiplayer interaction (simulated)", () => {
let harness: SimulatedHarness;
beforeEach(() => {
harness = createSimulatedHarness();
});
afterEach(() => {
harness.dispose();
});
it("should handle trade between players", async () => {
// Setup server service
const serverService = harness.server.createService(TradeService);
// Create two clients
const client1 = harness.createClient(1);
const client2 = harness.createClient(2);
const clientService1 = client1.createService(TradeService);
const clientService2 = client2.createService(TradeService);
// Players join
await harness.server.triggerPlayerJoining(1);
await harness.server.triggerPlayerJoining(2);
// Player 1 initiates trade
await clientService1.requestTrade({
source: 0,
args: [2], // Target player 2
});
// Player 2 accepts
await clientService2.acceptTrade({
source: 0,
args: [1], // From player 1
});
// Verify trade RPCs
harness.expectRpcCalled("TradeService:requestTrade");
harness.expectRpcCalled("TradeService:acceptTrade");
});
});Simulated Harness API
interface SimulatedHarness {
// Server environment
server: {
createService<T>(ServiceClass: new () => T): T;
triggerPlayerJoining(playerId: number): Promise<void>;
triggerPlayerDropped(playerId: number, reason?: string): Promise<void>;
getService<T>(ServiceClass: new () => T): T | undefined;
};
// Create client environments
createClient(playerId: number): ClientEnvironment;
getClient(playerId: number): ClientEnvironment | undefined;
// RPC tracking
expectRpcCalled(eventName: string, times?: number): void;
expectRpcCalledWith(eventName: string, ...args: unknown[]): void;
getRpcCalls(eventName?: string): RpcCall[];
clearRpcCalls(): void;
// Cleanup
dispose(): void;
}
interface ClientEnvironment {
playerId: number;
createService<T>(ServiceClass: new () => T): T;
getService<T>(ServiceClass: new () => T): T | undefined;
}E2E Tests (System)
For testing complete multi-player scenarios including event timelines and UI state. These extend simulated tests with support for multiple client connections, event sequence assertions, and snapshot capabilities.
Basic E2E Test
// test/e2e/character-selection.e2e.test.ts
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { createE2EHarness, type E2EHarness } from "@test/harness/e2e-harness";
import { CharacterService } from "@modules/core/characters/character.service";
describe("Character Selection (e2e)", () => {
let harness: E2EHarness;
beforeEach(() => {
harness = createE2EHarness();
});
afterEach(() => {
harness.dispose();
});
it("should complete character selection flow", async () => {
// Connect player
const player = await harness.connectClient(1, { name: "TestPlayer" });
// Verify player joined
expect(harness.server.getConnectedPlayers()).toContain(1);
// Select character
await player.performAction("CharacterService:selectCharacter", ["char-1"]);
// Verify UI state updated
const uiState = harness.getWebState(1);
expect(uiState.selectedCharacter).toBe("char-1");
// Verify event sequence
harness.expectEventSequence([
{ type: "client:created", playerId: 1 },
{ type: "player:joined", playerId: 1 },
{ type: "action:start", playerId: 1 },
{ type: "action:complete", playerId: 1 },
]);
});
});Multi-Client E2E Test
describe("Multiplayer E2E scenario", () => {
let harness: E2EHarness;
beforeEach(() => {
harness = createE2EHarness();
});
afterEach(() => {
harness.dispose();
});
it("should handle multiple players joining and interacting", async () => {
// Connect multiple players
const player1 = await harness.connectClient(1, { name: "Player1" });
const player2 = await harness.connectClient(2, { name: "Player2" });
const player3 = await harness.connectClient(3, { name: "Player3" });
// All players see each other
expect(harness.server.getConnectedPlayers()).toHaveLength(3);
// Player 1 sends message
await player1.performAction("ChatService:sendMessage", ["Hello everyone!"]);
// Verify message was broadcast
harness.expectRpcCalled("ChatService:broadcastMessage", 3);
// Player 2 disconnects
await harness.disconnectClient(2);
expect(harness.server.getConnectedPlayers()).toHaveLength(2);
});
});E2E Harness API
interface E2EHarness extends SimulatedHarness {
// Client connection
connectClient(playerId: number, options?: ClientOptions): Promise<E2EClient>;
disconnectClient(playerId: number): Promise<void>;
// Web/UI state
getWebState(playerId: number): Record<string, unknown>;
setWebState(playerId: number, state: Record<string, unknown>): void;
// Event timeline
getEventTimeline(): TimelineEvent[];
expectEventSequence(events: Partial<TimelineEvent>[]): void;
clearTimeline(): void;
}
interface E2EClient {
playerId: number;
performAction(eventName: string, args: unknown[]): Promise<unknown>;
getState(): ClientState;
}Web/UI Tests (Component)
For testing React components, hooks, UI state, and DOM interactions using jsdom.
Basic Component Test
// ui/src/components/MyComponent.test.tsx
import { describe, it, expect, afterEach } from "@jest/globals";
import { renderComponent, screen, cleanup } from "@test/utils/react-testing";
import MyComponent from "./MyComponent";
describe("MyComponent", () => {
afterEach(() => {
cleanup();
});
it("should render correctly", () => {
renderComponent(<MyComponent />);
expect(screen.getByText("Hello World")).toBeInTheDocument();
});
it("should display prop value", () => {
renderComponent(<MyComponent name="Test" />);
expect(screen.getByText("Hello Test")).toBeInTheDocument();
});
});Testing with User Interactions
import { describe, it, expect, afterEach } from "@jest/globals";
import { renderComponent, screen, cleanup, fireEvent, waitForUpdate } from "@test/utils/react-testing";
import Counter from "./Counter";
describe("Counter", () => {
afterEach(() => {
cleanup();
});
it("should increment on button click", async () => {
renderComponent(<Counter />);
expect(screen.getByText("Count: 0")).toBeInTheDocument();
fireEvent.click(screen.getByTestId("increment-btn"));
await waitForUpdate();
expect(screen.getByText("Count: 1")).toBeInTheDocument();
});
it("should handle input changes", async () => {
renderComponent(<Counter />);
fireEvent.change(screen.getByTestId("count-input"), { target: { value: "10" } });
await waitForUpdate();
expect(screen.getByText("Count: 10")).toBeInTheDocument();
});
});Testing with Services and Observables
import { describe, it, expect, afterEach, beforeEach } from "@jest/globals";
import { renderComponent, screen, cleanup, waitForUpdate, createTestObservable } from "@test/utils/react-testing";
import { registerMockService } from "@test/mocks/di/mock-di";
import { NeedsService } from "@modules/core/needs/needs.service";
import StatusPanel from "@modules/core/needs/huds/status";
// Create mock service with test observable
const createMockNeedsService = () => {
const hudState = createTestObservable({ hunger: 100, thirst: 100, health: 200, armor: 0 });
return { hudState };
};
describe("StatusPanel", () => {
let mockNeedsService: ReturnType<typeof createMockNeedsService>;
beforeEach(() => {
mockNeedsService = createMockNeedsService();
registerMockService(NeedsService, mockNeedsService);
});
afterEach(() => {
cleanup();
});
it("should display needs values", async () => {
renderComponent(<StatusPanel />);
expect(screen.getByText("100%")).toBeInTheDocument(); // hunger
});
it("should update when observable changes", async () => {
renderComponent(<StatusPanel />);
// Update observable
mockNeedsService.hudState.set({ hunger: 50, thirst: 75, health: 150, armor: 25 });
await waitForUpdate();
expect(screen.getByText("50%")).toBeInTheDocument();
});
});React Testing Utilities
// Available from @test/utils/react-testing
// Render a component (uses @testing-library/react)
function renderComponent(ui: ReactElement, options?: RenderOptions): RenderResult;
// Render with providers
function renderWithProviders(ui: ReactElement, providers: ComponentType[]): RenderResult;
// Clean up after tests
function cleanup(): void;
// Screen queries (re-exported from @testing-library/react)
const screen: Screen;
// Fire events (re-exported from @testing-library/react)
const fireEvent: FireEvent;
// Wait for state updates
function waitForUpdate(): Promise<void>;
// Create test observables
function createTestObservable<T>(initial: T): TestObservable<T>;
// Hook to use test observable in components
function useTestObservable<T>(observable: TestObservable<T>): T;
function TestProvider(props: { services: Record<string, unknown>; children: JSX.Element }): JSX.Element;FiveM Native Mocks
The testing framework provides comprehensive mocks for FiveM natives, including support for the SWC native-inliner plugin output.
Native Inliner Support
The SWC native-inliner transforms native calls like:
// Original code
const health = GetEntityHealth(ped);Into:
// Compiled output
const health = _in(hashHi, hashLo, ped, _r, _ri);The mock system provides all the runtime helpers that FiveM normally provides:
| Helper | FiveM Function | Purpose |
|---|---|---|
_in | Citizen.InvokeNative | Invoke native by hash |
_i | Citizen.PointerValueInt() | Int pointer marker |
_f | Citizen.PointerValueFloat() | Float pointer marker |
_v | Citizen.PointerValueVector() | Vector3 pointer marker |
_r | Citizen.ReturnResultAnyway() | Return result marker |
_ri | Citizen.ResultAsInteger() | Return as int |
_rf | Citizen.ResultAsFloat() | Return as float |
_rv | Citizen.ResultAsVector() | Return as Vector3 |
_s | Citizen.ResultAsString() | Return as string |
_fv | (helper) | Convert to float value |
_ts | (helper) | Convert to string (null-safe) |
_ch | (helper) | String to hash conversion |
Registering Custom Native Mocks
import { registerMockNative, joaat } from "@test/mocks/fivem/natives";
// Register by native name
registerMockNative("GET_ENTITY_HEALTH", (entity: number) => {
return mockHealthMap.get(entity) ?? 200;
});
// Register by hash (hashHi, hashLo)
registerMockNative(3708994389, 1943371007, (value: number) => {
return Math.abs(value); // ABSF native
});
// Use joaat for hashing strings
const propHash = joaat("prop_money_bag_01");Mock State Manipulation
import {
addMockPlayer,
removeMockPlayer,
setCurrentPlayerId,
setCurrentServerId,
setCurrentPedId,
setMockPedHealth,
setMockPedArmor,
setMockEntityCoords,
setMockConvar,
advanceMockGameTimer,
triggerNetEvent,
triggerEvent,
} from "@test/mocks/fivem/natives";
// Add/remove players
addMockPlayer(1, "TestPlayer", ["license:test123"]);
removeMockPlayer(1);
// Set client state
setCurrentPlayerId(1);
setCurrentServerId(1);
setCurrentPedId(1000);
// Set entity state
setMockPedHealth(1000, 150, 200);
setMockPedArmor(1000, 50);
setMockEntityCoords(1000, 100.0, 200.0, 30.0);
// Convars
setMockConvar("debug_mode", "true");
// Time
advanceMockGameTimer(1000); // Advance by 1 second
// Trigger events
triggerNetEvent("myNetEvent", arg1, arg2);
triggerEvent("myLocalEvent", arg1);RPC Mock System
The RPC mock system allows testing cross-runtime communication.
Basic Usage
import {
createMockRpc,
expectRpcCalled,
expectRpcCalledWith,
setRpcError,
setRpcDelay,
clearRpcCalls,
} from "@test/mocks/rpc/mock-rpc";
// Check if RPC was called
expectRpcCalled("MyService:getData");
expectRpcCalled("MyService:getData", 2); // Exactly 2 times
// Check call arguments
expectRpcCalledWith("MyService:getData", "arg1", "arg2");
// Simulate errors
setRpcError("MyService:getData", new Error("Network error"));
// Add delay for timing tests
setRpcDelay("MyService:getData", 100); // 100ms delay
// Clear tracking
clearRpcCalls();Mocking RPC Responses
import { mockRpcHandler } from "@test/mocks/rpc/mock-rpc";
// Mock a specific RPC handler
mockRpcHandler("MyService:getData", async (ctx) => {
const [id] = ctx.args;
return { id, name: "Mock Data" };
});
// Mock with different responses per call
let callCount = 0;
mockRpcHandler("MyService:getData", async () => {
callCount++;
if (callCount === 1) return { first: true };
return { first: false };
});Dependency Injection Mock
For testing services with mocked dependencies.
Basic Usage
import {
registerMockService,
createServiceInstance,
runInInjectionContext,
clearMockServices,
} from "@test/mocks/di/mock-di";
// Register mock services
registerMockService(CharacterService, mockCharacterService);
registerMockService(BankingService, mockBankingService);
// Create service with DI (uses registered mocks)
const myService = createServiceInstance(MyService);
// Run code in injection context
runInInjectionContext(() => {
const dep = inject(SomeDependency);
// dep is the mock if registered, otherwise real instance
});
// Clear all mocks
clearMockServices();Creating Mock Services
import { jest } from "@jest/globals";
// Create a mock service
const mockCharacterService = {
getActiveCharacter: jest.fn().mockReturnValue({ id: "1", name: "Test" }),
setActiveCharacter: jest.fn(),
characters: {
value: [{ id: "1", name: "Test" }],
subscribe: jest.fn(),
},
};
// Register and use
registerMockService(CharacterService, mockCharacterService);
const myService = createServiceInstance(MyService);
// Verify calls
expect(mockCharacterService.getActiveCharacter).toHaveBeenCalledWith(1);Debugging Tests
Enable Debug Output
# Show console output during tests
DEBUG_TESTS=1 pnpm test:unitRun Specific Tests
# Run specific file
pnpm test -- path/to/test.ts
# Run tests matching pattern
pnpm test -- --testNamePattern="should handle"
# Run only failed tests
pnpm test -- --onlyFailuresWatch Mode Options
# Watch specific files
pnpm test:watch -- path/to/test.ts
# Watch and run only related tests
pnpm test:watch -- --onlyChangedBest Practices
1. Isolate Tests
Each test should be independent. Use beforeEach to set up fresh state:
let service: MyService;
beforeEach(() => {
service = createServiceInstance(MyService);
clearRpcCalls();
resetMockState();
});2. Clean Up Resources
Always dispose harnesses and unmount components:
afterEach(() => {
harness?.dispose();
cleanup(); // For UI tests
});3. Use Appropriate Test Type
- Unit tests → Pure logic, utilities, algorithms
- Server/Client tests → Runtime-specific unit tests (observables, natives, entities)
- Simulated tests → Integration tests for cross-runtime flows (RPC, state sync) — runs in CI/CD
- E2E tests → System tests for multi-player scenarios and complex user journeys
- Web tests → Component tests for React UI
4. Mock External Dependencies
// Mock database
registerMockService(Repository, mockRepository);
// Mock RPC responses
mockRpcHandler("ExternalService:getData", async () => mockData);
// Mock timers
jest.useFakeTimers();5. Assert RPC Communication
// Verify RPC was called
harness.expectRpcCalled("MyService:getData");
// Verify with specific args
harness.expectRpcCalledWith("MyService:getData", "expected-arg");
// Verify call count
harness.expectRpcCalled("MyService:getData", 2);6. Test Event Sequences (E2E)
harness.expectEventSequence([
{ type: "player:joined", playerId: 1 },
{ type: "character:selected", playerId: 1 },
{ type: "spawn:complete", playerId: 1 },
]);7. Wait for Async Updates
// For UI tests
await waitForUpdate();
// For RPC tests
await harness.flush(); // Process pending RPCsConfiguration
Jest Configuration
The Jest configuration is in jest.config.ts:
// jest.config.ts
export default {
projects: [
{
displayName: "unit",
testEnvironment: "node",
testMatch: ["**/*.test.ts", "**/*.spec.ts"],
testPathIgnorePatterns: [
"\\.server\\.test\\.ts$",
"\\.client\\.test\\.ts$",
"\\.simulated\\.test\\.ts$",
"\\.e2e\\.test\\.ts$",
],
},
{
displayName: "server",
testEnvironment: "node",
testMatch: ["**/*.server.test.ts"],
setupFilesAfterEnv: ["<rootDir>/test/setup/server.setup.ts"],
},
// ... more projects
],
};TypeScript Paths
The @test/* path alias is configured in tsconfig.base.json:
{
"compilerOptions": {
"paths": {
"@test/*": ["./test/*"]
}
}
}