docs(frontend): Add MSW testing guide for frontend development (#12131)

This commit is contained in:
sp.wack 2025-12-23 16:32:27 +04:00 committed by GitHub
parent 2b8f779b65
commit a9d2f72d72
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

146
frontend/__tests__/MSW.md Normal file
View File

@ -0,0 +1,146 @@
# Mock Service Worker (MSW) Guide
## Overview
[Mock Service Worker (MSW)](https://mswjs.io/) is an API mocking library that intercepts outgoing network requests at the network level. Unlike traditional mocking that patches `fetch` or `axios`, MSW uses a Service Worker in the browser and direct request interception in Node.js—making mocks transparent to your application code.
We use MSW in this project for:
- **Testing**: Write reliable unit and integration tests without real network calls
- **Development**: Run the frontend with mocked APIs when the backend isn't available or when working on features with pending backend APIs
The same mock handlers work in both environments, so you write them once and reuse everywhere.
## Relevant Files
- `src/mocks/handlers.ts` - Main handler registry that combines all domain handlers
- `src/mocks/*-handlers.ts` - Domain-specific handlers (auth, billing, conversation, etc.)
- `src/mocks/browser.ts` - Browser setup for development mode
- `src/mocks/node.ts` - Node.js setup for tests
- `vitest.setup.ts` - Global test setup with MSW lifecycle hooks
## Development Workflow
### Running with Mocked APIs
```sh
# Run with API mocking enabled
npm run dev:mock
# Run with API mocking + SaaS mode simulation
npm run dev:mock:saas
```
These commands set `VITE_MOCK_API=true` which activates the MSW Service Worker to intercept requests.
> [!NOTE]
> **OSS vs SaaS Mode**
>
> OpenHands runs in two modes:
> - **OSS mode**: For local/self-hosted deployments where users provide their own LLM API keys and configure git providers manually
> - **SaaS mode**: For the cloud offering with billing, managed API keys, and OAuth-based GitHub integration
>
> Use `dev:mock:saas` when working on SaaS-specific features like billing, API key management, or subscription flows.
## Writing Tests
### Service Layer Mocking (Recommended)
For most tests, mock at the service layer using `vi.spyOn`. This approach is explicit, test-scoped, and makes the scenario being tested clear.
```typescript
import { vi } from "vitest";
import SettingsService from "#/api/settings-service/settings-service.api";
const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
getSettingsSpy.mockResolvedValue({
llm_model: "openai/gpt-4o",
llm_api_key_set: true,
// ... other settings
});
```
Use `mockResolvedValue` for success scenarios and `mockRejectedValue` for error scenarios:
```typescript
getSettingsSpy.mockRejectedValue(new Error("Failed to fetch settings"));
```
### Network Layer Mocking (Advanced)
For tests that need actual network-level behavior (WebSockets, testing retry logic, etc.), use `server.use()` to override handlers per test.
> [!IMPORTANT]
> **Reuse the global server instance** - Don't create new `setupServer()` calls in individual tests. The project already has a global MSW server configured in `vitest.setup.ts` that handles lifecycle (`server.listen()`, `server.resetHandlers()`, `server.close()`). Use `server.use()` to add runtime handlers for specific test scenarios.
```typescript
import { http, HttpResponse } from "msw";
import { server } from "#/mocks/node";
it("should handle server errors", async () => {
server.use(
http.get("/api/my-endpoint", () => {
return new HttpResponse(null, { status: 500 });
}),
);
// ... test code
});
```
For WebSocket testing, see `__tests__/helpers/msw-websocket-setup.ts` for utilities.
## Adding New API Mocks
When adding new API endpoints, create mocks in both places to maintain 1:1 similarity with the backend:
### 1. Add to `src/mocks/` (for development)
Create or update a domain-specific handler file:
```typescript
// src/mocks/my-feature-handlers.ts
import { http, HttpResponse } from "msw";
export const MY_FEATURE_HANDLERS = [
http.get("/api/my-feature", () => {
return HttpResponse.json({
data: "mock response",
});
}),
];
```
Register in `handlers.ts`:
```typescript
import { MY_FEATURE_HANDLERS } from "./my-feature-handlers";
export const handlers = [
// ... existing handlers
...MY_FEATURE_HANDLERS,
];
```
### 2. Mock in tests for specific scenarios
In your test files, spy on the service method to control responses per test case:
```typescript
import { vi } from "vitest";
import MyFeatureService from "#/api/my-feature-service.api";
const spy = vi.spyOn(MyFeatureService, "getData");
spy.mockResolvedValue({ data: "test-specific response" });
```
See `__tests__/routes/llm-settings.test.tsx` for a real-world example of service layer mocking.
> [!TIP]
> For guidance on creating service APIs, see `src/api/README.md`.
## Best Practices
- **Keep mocks close to real API contracts** - Update mocks when backend changes
- **Use service layer mocking for most tests** - It's simpler and more explicit
- **Reserve network layer mocking for integration tests** - WebSockets, retry logic, etc.
- **Export mock data from handler files** - Reuse in tests (e.g., `MOCK_DEFAULT_USER_SETTINGS`)