Integration Testing
Integration tests validate that the Fetcher ecosystem works correctly against real HTTP APIs. The integration-test workspace contains tests that make actual network calls to public APIs and running backend services.
Workspace Structure
integration-test/
src/
fetcher/
typicodeFetcher.ts # NamedFetcher for JSONPlaceholder
decorator/
typicodeUserService.ts # Decorator-based API client
typicodePostService.ts
resultExtractorService.ts
wow/
cart/
cartCommandClient.ts # CQRS command client
cartClientOptions.ts
exampleFetcher.ts
generated/
example/ # Auto-generated from OpenAPI spec
test/
fetcher/
typicodeFetcher.test.ts # Fetcher integration tests
decorator/
typicodeUserService.test.ts # Decorator integration tests
typicodePostService.test.ts
resultExtractorService.test.ts
wow/
cart/
cartCommandClient.test.ts
openai/
openai.test.tsSource: integration-test/
Test API: JSONPlaceholder
The primary test API used for integration tests is JSONPlaceholder, a free public REST API. It provides realistic CRUD endpoints without requiring authentication.
Fetcher Setup
import { NamedFetcher } from '@ahoo-wang/fetcher';
export const typicodeFetcher = new NamedFetcher('typicode', {
baseURL: 'https://jsonplaceholder.typicode.com',
});
// Optional: add interceptors
typicodeFetcher.interceptors.request.use(cosecRequestInterceptor);
typicodeFetcher.interceptors.response.use(authorizationResponseInterceptor);Source: integration-test/src/fetcher/typicodeFetcher.ts
Integration Test Examples
Fetcher-Level Tests
Tests using the Fetcher class directly against JSONPlaceholder:
import { describe, it, expect } from 'vitest';
import { typicodeFetcher } from '../../src';
import { HttpMethod, ResultExtractors } from '@ahoo-wang/fetcher';
describe('typicodeFetcher Integration Test', () => {
it('should fetch posts from typicode API', async () => {
const response = await typicodeFetcher.get('/posts');
expect(response).toBeDefined();
const posts = await response.json();
expect(Array.isArray(posts)).toBe(true);
expect(posts.length).toBeGreaterThan(0);
const post = posts[0];
expect(post).toHaveProperty('id');
expect(post).toHaveProperty('userId');
expect(post).toHaveProperty('title');
expect(post).toHaveProperty('body');
});
it('should create a new post', async () => {
const newPost = { userId: 1, title: 'Test Post', body: 'Content' };
const response = await typicodeFetcher.post('/posts', {
body: JSON.stringify(newPost),
});
const post = await response.json();
expect(post).toHaveProperty('id');
expect(post.title).toBe(newPost.title);
});
it('should update a post', async () => {
const response = await typicodeFetcher.put('/posts/1', {
body: { userId: 1, title: 'Updated', body: 'Updated content' },
});
const post = await response.json();
expect(post.title).toBe('Updated');
});
it('should delete a post', async () => {
const response = await typicodeFetcher.delete('/posts/1');
const result = await response.json();
expect(result).toEqual({});
});
});Source: integration-test/test/fetcher/typicodeFetcher.test.ts
Decorator-Level Tests
Tests using decorator-based API services:
import { describe, it, expect } from 'vitest';
import { typicodeUserService } from '../../src';
describe('TypicodeUserService Integration Test', () => {
it('should get user albums', async () => {
const albums = await typicodeUserService.getAlbums('1');
expect(albums).toBeDefined();
expect(Array.isArray(albums)).toBe(true);
if (albums.length > 0) {
expect(albums[0]).toHaveProperty('id');
expect(albums[0]).toHaveProperty('userId');
expect(albums[0]).toHaveProperty('title');
}
});
it('should get user todos', async () => {
const todos = await typicodeUserService.getTodos('1');
expect(todos).toBeDefined();
expect(Array.isArray(todos)).toBe(true);
});
it('should get user posts', async () => {
const posts = await typicodeUserService.getPosts('1');
expect(posts).toBeDefined();
expect(Array.isArray(posts)).toBe(true);
});
});Source: integration-test/test/decorator/typicodeUserService.test.ts
Test Execution Flow
sequenceDiagram
autonumber
participant VT as Vitest
participant FT as Fetcher Tests
participant DT as Decorator Tests
participant WT as Wow Tests
participant API as JSONPlaceholder API
VT->>FT: Run fetcher tests
FT->>API: GET /posts
API-->>FT: Posts array
FT->>API: POST /posts
API-->>FT: Created post
FT->>API: PUT /posts/1
API-->>FT: Updated post
FT->>API: DELETE /posts/1
API-->>FT: Empty object
FT-->>VT: Results
VT->>DT: Run decorator tests
DT->>API: GET /users/1/albums
API-->>DT: Albums array
DT->>API: GET /users/1/todos
API-->>DT: Todos array
DT-->>VT: Results
VT->>WT: Run Wow tests
Note over WT: Requires running<br>Wow backend server
WT-->>VT: ResultsEnvironment Configuration
Package Configuration
{
"scripts": {
"test": "vitest run --coverage",
"generate": "pnpm exec fetcher-generator generate -i http://localhost:8080/v3/api-docs"
}
}Source: integration-test/package.json
Vite Configuration
// integration-test/vite.config.ts
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'FetcherIt',
fileName: format => `index.${format}.js`,
},
rollupOptions: {
external: [
'@ahoo-wang/fetcher',
'@ahoo-wang/fetcher-decorator',
'@ahoo-wang/fetcher-eventstream',
'@ahoo-wang/fetcher-cosec',
'@ahoo-wang/fetcher-wow',
],
},
},
});Source: integration-test/vite.config.ts
Running Integration Tests
# From root
pnpm test:it
# From integration-test directory
cd integration-test && pnpm test
# Specific test file
pnpm --filter @ahoo-wang/fetcher-integration-test vitest run test/fetcher/typicodeFetcher.test.tsIntegration Test Categories
graph TD
subgraph sg_1 ["No Backend Required"]
A1["Fetcher CRUD tests<br>(JSONPlaceholder)"]
A2["Decorator service tests<br>(JSONPlaceholder)"]
A3["Result extractor tests<br>(JSONPlaceholder)"]
end
subgraph sg_2 ["Backend Required"]
B1["Wow Command tests<br>(localhost:8080)"]
B2["Wow Query tests<br>(localhost:8080)"]
B3["CoSec tests<br>(localhost:8080)"]
B4["Generated client tests<br>(localhost:8080)"]
end
A1 --> C["Public API"]
A2 --> C
A3 --> C
B1 --> D["Local Wow Server"]
B2 --> D
B3 --> D
B4 --> D
style A1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style A2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style A3 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style B1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style B2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style B3 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style B4 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style D fill:#2d333b,stroke:#6d5dfc,color:#e6edf3Wow CQRS Integration Tests
The Wow integration tests validate command/event-sourcing patterns against a running Wow backend:
- CartCommandClient: Sends commands (AddCartItem, RemoveCartItem, ChangeQuantity)
- EventStreamQueryClient: Queries event streams via SSE
- SnapshotQueryClient: Queries aggregate snapshots
- Generated clients: Tests auto-generated code from OpenAPI spec
These tests require a running backend server at localhost:8080 and are typically skipped in CI unless the server is available.
Integration Test Coverage Map
graph LR
subgraph sg_1 ["Fetcher Package"]
F1["GET requests"]
F2["POST requests"]
F3["PUT requests"]
F4["DELETE requests"]
F5["PATCH requests"]
F6["Result extractors"]
end
subgraph sg_2 ["Decorator Package"]
D1["@api + @get"]
D2["@path + @query"]
D3["@body + @post"]
D4["Result extractor<br>override"]
end
subgraph sg_3 ["Wow Package"]
W1["Command clients"]
W2["Event stream queries"]
W3["Snapshot queries"]
end
A["JSONPlaceholder<br>API"] --> F1
A --> F2
A --> F3
A --> F4
A --> D1
A --> D2
A --> D3
B["Wow Backend<br>Server"] --> W1
B --> W2
B --> W3
style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style F1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style D1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style W1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3Related Pages
- Testing Overview -- Testing strategy overview
- Unit Testing -- Unit testing guide
- Browser Testing -- Browser and component testing
- Fetcher Client API -- API being tested