- Authors

- Name
- Youngju Kim
- @fjvbn20031
Overview
In modern software development, frontend and backend teams almost always work in parallel. Waiting for backend APIs to be ready before starting frontend work, or being unable to access third-party services during testing, is a constant friction point.
A Mock Server is the key tool for solving these problems. By defining the API contract first, you can develop and test the frontend without a real backend.
1. Why You Need a Mock Server
1.1 Parallel Frontend-Backend Development
Traditional approach (sequential):
Backend API complete → Frontend development begins
└── Delay: frontend blocked waiting for backend
With a mock server (parallel):
API spec defined → Backend dev || Frontend dev
↑ No blocking via mock server
Both teams can work simultaneously as long as they agree on the API contract first.
1.2 Removing External API Dependencies
Third-party services like payment APIs, mapping services, or weather APIs come with problems:
- Costs: APIs that charge per call
- Rate Limiting: Slows down test execution
- Service Instability: External outages break CI/CD pipelines
- Test Data Pollution: Test data mixing into production data
1.3 Reproducing Error Scenarios
A mock server makes it easy to reproduce situations that are hard to trigger in real services:
- Network timeouts
- 500 Internal Server Error
- 429 Too Many Requests
- Network disconnection
- Response delays (slow network simulation)
1.4 Test Environment Stability
Eliminating external dependencies in CI/CD:
No real DB → InMemory DB Fake
No real API → Mock Server
No real queue → In-process Fake
2. json-server (Fastest Start)
json-server lets you create a full REST API server from a single JSON file in under 30 seconds.
2.1 Installation and Basic Usage
# Global install
npm install -g json-server
# Or as a dev dependency
npm install --save-dev json-server
2.2 db.json Structure
{
"users": [
{ "id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin" },
{ "id": 2, "name": "Bob", "email": "bob@example.com", "role": "user" }
],
"posts": [
{ "id": 1, "title": "First Post", "body": "Content here", "userId": 1, "published": true },
{ "id": 2, "title": "Second Post", "body": "Content here", "userId": 2, "published": false }
],
"comments": [{ "id": 1, "text": "Great post!", "postId": 1, "userId": 2 }],
"profile": {
"name": "Admin",
"version": "1.0.0"
}
}
Start json-server:
json-server --watch db.json --port 3001
Auto-generated endpoints:
GET /users
GET /users/1
POST /users
PUT /users/1
PATCH /users/1
DELETE /users/1
GET /posts?userId=1
GET /posts?published=true
GET /posts?_page=1&_limit=10
GET /posts?_sort=title&_order=asc
2.3 Custom Routing (routes.json)
{
"/api/*": "/$1",
"/api/v1/users/:id": "/users/:id",
"/blog/:id/comments": "/comments?postId=:id"
}
Apply routing on startup:
json-server --watch db.json --routes routes.json --port 3001
2.4 Adding Middleware
// middleware.js
module.exports = (req, res, next) => {
// Check auth header
if (req.headers.authorization !== 'Bearer valid-token') {
if (req.method !== 'GET') {
return res.status(401).json({ error: 'Unauthorized' })
}
}
// Simulate response delay (500ms)
setTimeout(next, 500)
}
json-server --watch db.json --middlewares middleware.js
2.5 Relational Data Modeling
json-server supports relational data via _embed and _expand:
# Get post with embedded comments
GET /posts/1?_embed=comments
# Get post with expanded user info
GET /posts?_expand=user
# Nested relationships
GET /users?_embed=posts
2.6 Pagination, Sorting, and Filtering
# Pagination (10 per page, page 2)
GET /posts?_page=2&_limit=10
# Sort by title descending
GET /posts?_sort=title&_order=desc
# Filter published posts
GET /posts?published=true
# Full-text search
GET /posts?q=search-term
# Range filter
GET /posts?id_gte=5&id_lte=10
2.7 Custom Server Script
// server.js - for finer control
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()
server.use(middlewares)
// Custom routes
server.post('/auth/login', (req, res) => {
const { email, password } = req.body
if (email === 'admin@example.com' && password === 'password') {
res.json({ token: 'fake-jwt-token', userId: 1 })
} else {
res.status(401).json({ error: 'Invalid credentials' })
}
})
server.use(router)
server.listen(3001, () => {
console.log('JSON Server is running on port 3001')
})
3. MSW (Mock Service Worker) - The Modern Approach
MSW uses Service Workers to intercept requests at the network level. It lets you reuse the same handlers in both the browser and Node.js.
3.1 Installation and Setup
npm install msw --save-dev
# Generate Service Worker file in the public directory
npx msw init public/ --save
3.2 Writing REST API Handlers
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
// GET list of users
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
])
}),
// GET specific user
http.get('/api/users/:id', ({ params }) => {
const { id } = params
if (id === '999') {
return new HttpResponse(null, { status: 404 })
}
return HttpResponse.json({
id: Number(id),
name: 'Alice',
email: 'alice@example.com',
})
}),
// POST create user
http.post('/api/users', async ({ request }) => {
const newUser = await request.json()
return HttpResponse.json({ id: Date.now(), ...newUser }, { status: 201 })
}),
// PUT update user
http.put('/api/users/:id', async ({ params, request }) => {
const { id } = params
const updates = await request.json()
return HttpResponse.json({ id: Number(id), ...updates })
}),
// DELETE user
http.delete('/api/users/:id', ({ params }) => {
return new HttpResponse(null, { status: 204 })
}),
]
3.3 GraphQL Handlers
import { graphql, HttpResponse } from 'msw'
export const graphqlHandlers = [
graphql.query('GetUser', ({ variables }) => {
const { id } = variables
return HttpResponse.json({
data: {
user: {
id,
name: 'Alice',
email: 'alice@example.com',
posts: [{ id: 1, title: 'First Post' }],
},
},
})
}),
graphql.mutation('CreateUser', ({ variables }) => {
const { name, email } = variables
return HttpResponse.json({
data: {
createUser: {
id: String(Date.now()),
name,
email,
},
},
})
}),
]
3.4 Dynamic Responses Using Request Parameters
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/products', ({ request }) => {
const url = new URL(request.url)
const category = url.searchParams.get('category')
const page = Number(url.searchParams.get('page') ?? '1')
const limit = Number(url.searchParams.get('limit') ?? '10')
const allProducts = [
{ id: 1, name: 'Laptop', category: 'electronics', price: 1200 },
{ id: 2, name: 'Mouse', category: 'electronics', price: 30 },
{ id: 3, name: 'Desk', category: 'furniture', price: 250 },
]
let filtered = allProducts
if (category) {
filtered = allProducts.filter((p) => p.category === category)
}
const start = (page - 1) * limit
const data = filtered.slice(start, start + limit)
return HttpResponse.json({
data,
total: filtered.length,
page,
limit,
})
}),
]
3.5 Error Response Scenarios
import { http, HttpResponse } from 'msw'
// Pattern to dynamically control error scenarios
let shouldFailNext = false
export const handlers = [
// Test-only control endpoint
http.post('/test/simulate-error', async ({ request }) => {
const { fail } = await request.json()
shouldFailNext = fail
return new HttpResponse(null, { status: 200 })
}),
http.post('/api/payment', async ({ request }) => {
if (shouldFailNext) {
shouldFailNext = false
return HttpResponse.json(
{ error: 'PAYMENT_DECLINED', message: 'Card was declined' },
{ status: 402 }
)
}
const { amount } = await request.json()
return HttpResponse.json({
paymentId: `PAY-${Date.now()}`,
amount,
status: 'SUCCESS',
})
}),
// Network error simulation
http.get('/api/unstable', () => {
if (Math.random() > 0.7) {
return HttpResponse.error()
}
return HttpResponse.json({ data: 'success' })
}),
]
3.6 Browser Environment Setup
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
// src/main.tsx (React entry point)
async function enableMocking() {
if (process.env.NODE_ENV !== 'development') {
return
}
const { worker } = await import('./mocks/browser')
return worker.start({
onUnhandledRequest: 'bypass' // Pass through unhandled requests
})
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
})
3.7 Node.js Environment Setup (for Tests)
// src/mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
// jest.setup.ts
import { server } from './src/mocks/server'
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
// Override handlers in specific tests
import { server } from '../mocks/server'
import { http, HttpResponse } from 'msw'
test('handles API error gracefully', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json({ error: 'Server Error' }, { status: 500 })
})
)
const result = await fetchUsers()
expect(result.error).toBe('Server Error')
})
3.8 MSW in Next.js
// next.config.js
const nextConfig = {
async headers() {
return [
{
source: '/mockServiceWorker.js',
headers: [{ key: 'Service-Worker-Allowed', value: '/' }],
},
]
},
}
// src/app/providers.tsx (App Router)
'use client'
import { useEffect } from 'react'
export function MSWProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
import('../mocks/browser').then(({ worker }) => {
worker.start({ onUnhandledRequest: 'bypass' })
})
}
}, [])
return children
}
4. WireMock - Java/JVM Environment
WireMock is the most widely used HTTP mock server in the Java/JVM ecosystem.
4.1 Running WireMock Standalone
# Download WireMock
curl -o wiremock.jar https://repo1.maven.org/maven2/com/github/tomakehurst/wiremock-jre8-standalone/2.35.0/wiremock-jre8-standalone-2.35.0.jar
# Run
java -jar wiremock.jar --port 8080 --verbose
Directory structure:
wiremock/
├── mappings/ # Stub definition JSON files
│ ├── users.json
│ └── orders.json
└── __files/ # Response body files
├── users.json
└── order-template.json
4.2 Defining Stubs with JSON Files
{
"request": {
"method": "GET",
"url": "/api/users"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": [
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
{ "id": 2, "name": "Bob", "email": "bob@example.com" }
]
}
}
4.3 Request Matching (URL, Headers, Body)
{
"request": {
"method": "POST",
"url": "/api/orders",
"headers": {
"Content-Type": {
"equalTo": "application/json"
},
"Authorization": {
"matches": "Bearer .+"
}
},
"bodyPatterns": [
{
"matchesJsonPath": "$.items[*].productId"
},
{
"equalToJson": {
"userId": 1
},
"ignoreArrayOrder": true,
"ignoreExtraElements": true
}
]
},
"response": {
"status": 201,
"jsonBody": {
"orderId": "ORDER-001",
"status": "PENDING"
}
}
}
4.4 Response Templating (Handlebars)
{
"request": {
"method": "GET",
"urlPathPattern": "/api/users/([0-9]+)"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "{ \"id\": {{request.pathSegments.[2]}}, \"name\": \"User-{{request.pathSegments.[2]}}\", \"timestamp\": \"{{now}}\" }",
"transformers": ["response-template"]
}
}
4.5 Scenarios (State-Based Mocking)
Scenarios let you return different responses based on current state:
{
"scenarioName": "Order Processing Flow",
"requiredScenarioState": "Started",
"newScenarioState": "Payment Complete",
"request": {
"method": "POST",
"url": "/api/orders/pay"
},
"response": {
"status": 200,
"jsonBody": { "status": "PAID" }
}
}
{
"scenarioName": "Order Processing Flow",
"requiredScenarioState": "Payment Complete",
"newScenarioState": "Shipping",
"request": {
"method": "GET",
"url": "/api/orders/ORDER-001"
},
"response": {
"status": 200,
"jsonBody": { "status": "SHIPPING" }
}
}
4.6 Record and Playback
Record real API responses and replay them:
# Run in record mode (proxy to real API)
java -jar wiremock.jar --proxy-all="https://api.example.com" --record-mappings --port 8080
# Replay recorded responses (default mode)
java -jar wiremock.jar --port 8080
5. Prism - OpenAPI-Based Mock Server
Prism automatically generates a mock server from an OpenAPI (Swagger) specification.
5.1 Installation and Usage
npm install -g @stoplight/prism-cli
# Run a mock server from an OpenAPI spec
npx @stoplight/prism-cli mock api.yaml --port 4010
5.2 OpenAPI Spec Example
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
operationId: getUsers
responses:
'200':
description: List of users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
examples:
default:
value:
- id: 1
name: Alice
email: alice@example.com
post:
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required: [id, name, email]
properties:
id:
type: integer
name:
type: string
email:
type: string
format: email
CreateUserRequest:
type: object
required: [name, email]
properties:
name:
type: string
email:
type: string
format: email
5.3 Dynamic Response Generation
Prism generates values automatically from schemas when no example is provided:
# Dynamic mode (schema-based random responses)
npx @stoplight/prism-cli mock api.yaml --dynamic
5.4 Validation Mode
# Validation mode - validates requests and responses against the spec
npx @stoplight/prism-cli proxy api.yaml https://api.example.com --validate-request --validate-response
6. Building a Mock Server in Go
The Go standard library is sufficient to build a complete mock server.
6.1 Core Mock Server Structure
package mockserver
import (
"encoding/json"
"log"
"net/http"
"sync"
"time"
)
type MockServer struct {
server *http.Server
mux *http.ServeMux
mu sync.RWMutex
stubs map[string]*Stub
calls []RequestLog
}
type Stub struct {
Method string
Path string
StatusCode int
Body interface{}
Headers map[string]string
Delay time.Duration
}
type RequestLog struct {
Method string
Path string
Headers map[string][]string
Body []byte
Timestamp time.Time
}
func NewMockServer(port string) *MockServer {
ms := &MockServer{
mux: http.NewServeMux(),
stubs: make(map[string]*Stub),
}
ms.server = &http.Server{
Addr: ":" + port,
Handler: ms.mux,
}
ms.mux.HandleFunc("/", ms.handleRequest)
// Admin control endpoints
ms.mux.HandleFunc("/__admin/stubs", ms.handleAdminStubs)
ms.mux.HandleFunc("/__admin/calls", ms.handleAdminCalls)
ms.mux.HandleFunc("/__admin/reset", ms.handleAdminReset)
return ms
}
func (ms *MockServer) Start() {
go func() {
log.Printf("MockServer starting on %s", ms.server.Addr)
if err := ms.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("MockServer failed: %v", err)
}
}()
time.Sleep(100 * time.Millisecond)
}
func (ms *MockServer) Stop() {
ms.server.Close()
}
func (ms *MockServer) AddStub(stub *Stub) {
ms.mu.Lock()
defer ms.mu.Unlock()
key := stub.Method + ":" + stub.Path
ms.stubs[key] = stub
}
func (ms *MockServer) handleRequest(w http.ResponseWriter, r *http.Request) {
// Log the request
ms.mu.Lock()
ms.calls = append(ms.calls, RequestLog{
Method: r.Method,
Path: r.URL.Path,
Headers: r.Header,
Timestamp: time.Now(),
})
ms.mu.Unlock()
// Look up stub
ms.mu.RLock()
key := r.Method + ":" + r.URL.Path
stub, ok := ms.stubs[key]
ms.mu.RUnlock()
if !ok {
http.Error(w, "No stub found for "+r.Method+" "+r.URL.Path, http.StatusNotFound)
return
}
// Simulate response delay
if stub.Delay > 0 {
time.Sleep(stub.Delay)
}
// Set headers
for k, v := range stub.Headers {
w.Header().Set(k, v)
}
if w.Header().Get("Content-Type") == "" {
w.Header().Set("Content-Type", "application/json")
}
w.WriteHeader(stub.StatusCode)
if stub.Body != nil {
json.NewEncoder(w).Encode(stub.Body)
}
}
func (ms *MockServer) handleAdminReset(w http.ResponseWriter, r *http.Request) {
ms.mu.Lock()
ms.stubs = make(map[string]*Stub)
ms.calls = nil
ms.mu.Unlock()
w.WriteHeader(http.StatusOK)
}
func (ms *MockServer) handleAdminCalls(w http.ResponseWriter, r *http.Request) {
ms.mu.RLock()
defer ms.mu.RUnlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(ms.calls)
}
func (ms *MockServer) handleAdminStubs(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var stub Stub
if err := json.NewDecoder(r.Body).Decode(&stub); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ms.AddStub(&stub)
w.WriteHeader(http.StatusCreated)
return
}
ms.mu.RLock()
defer ms.mu.RUnlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(ms.stubs)
}
6.2 Using the Mock Server in Tests
func TestUserService_GetUser(t *testing.T) {
ms := NewMockServer("9999")
ms.Start()
defer ms.Stop()
ms.AddStub(&Stub{
Method: "GET",
Path: "/api/users/1",
StatusCode: 200,
Body: map[string]interface{}{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
},
})
client := NewUserServiceClient("http://localhost:9999")
user, err := client.GetUser(1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("expected Alice, got %s", user.Name)
}
}
func TestUserService_GetUser_NotFound(t *testing.T) {
ms := NewMockServer("9998")
ms.Start()
defer ms.Stop()
ms.AddStub(&Stub{
Method: "GET",
Path: "/api/users/999",
StatusCode: 404,
Body: map[string]string{"error": "User not found"},
})
client := NewUserServiceClient("http://localhost:9998")
_, err := client.GetUser(999)
if err == nil {
t.Error("expected error, got nil")
}
}
7. Building a Mock Server in TypeScript/Node.js
7.1 Express.js + TypeScript Mock Server
// mock-server/index.ts
import express, { Request, Response, NextFunction } from 'express'
import fs from 'fs'
import path from 'path'
interface StubDefinition {
method: string
path: string
statusCode: number
body?: unknown
headers?: Record<string, string>
delay?: number
}
const app = express()
app.use(express.json())
const stubs: Map<string, StubDefinition> = new Map()
const requestLog: Array<{ method: string; path: string; timestamp: string }> = []
// Request logging middleware
app.use((req: Request, res: Response, next: NextFunction) => {
requestLog.push({
method: req.method,
path: req.path,
timestamp: new Date().toISOString(),
})
next()
})
// Admin API
app.post('/__admin/stubs', (req: Request, res: Response) => {
const stub: StubDefinition = req.body
const key = `${stub.method}:${stub.path}`
stubs.set(key, stub)
res.status(201).json({ message: 'Stub registered', key })
})
app.delete('/__admin/stubs', (req: Request, res: Response) => {
stubs.clear()
requestLog.length = 0
res.status(200).json({ message: 'All stubs cleared' })
})
app.get('/__admin/calls', (req: Request, res: Response) => {
res.json(requestLog)
})
// Dynamic response handler
app.use(async (req: Request, res: Response) => {
const key = `${req.method}:${req.path}`
const stub = stubs.get(key)
if (!stub) {
return res.status(404).json({
error: 'No stub found',
request: { method: req.method, path: req.path },
})
}
// Response delay
if (stub.delay && stub.delay > 0) {
await new Promise((resolve) => setTimeout(resolve, stub.delay))
}
// Set headers
if (stub.headers) {
Object.entries(stub.headers).forEach(([key, value]) => {
res.setHeader(key, value)
})
}
res.status(stub.statusCode).json(stub.body)
})
const PORT = process.env.MOCK_PORT ?? '3001'
app.listen(PORT, () => {
console.log(`Mock server running on port ${PORT}`)
})
export { app }
7.2 File-Based Response Routing
// Manage responses via directory structure
// responses/
// GET/
// api/
// users/
// index.json -> GET /api/users
// 1.json -> GET /api/users/1
// POST/
// api/
// users/
// index.json -> POST /api/users (response template)
app.use((req: Request, res: Response) => {
const responsePath = path.join(__dirname, 'responses', req.method, req.path, 'index.json')
if (fs.existsSync(responsePath)) {
const responseData = JSON.parse(fs.readFileSync(responsePath, 'utf-8'))
res.json(responseData)
} else {
res.status(404).json({ error: 'No response file found' })
}
})
7.3 Middleware for Delay and Error Injection
// chaos-middleware.ts - middleware for chaos engineering
interface ChaosConfig {
errorRate: number // 0.0 to 1.0
minDelay: number // ms
maxDelay: number // ms
}
let chaosConfig: ChaosConfig = {
errorRate: 0,
minDelay: 0,
maxDelay: 0,
}
export const chaosMiddleware = async (req: Request, res: Response, next: NextFunction) => {
// Response delay
const delay = chaosConfig.minDelay + Math.random() * (chaosConfig.maxDelay - chaosConfig.minDelay)
if (delay > 0) {
await new Promise((resolve) => setTimeout(resolve, delay))
}
// Random error injection
if (Math.random() < chaosConfig.errorRate) {
return res.status(500).json({
error: 'Chaos monkey strikes!',
timestamp: new Date().toISOString(),
})
}
next()
}
// Config update API
app.post('/__admin/chaos', (req: Request, res: Response) => {
chaosConfig = { ...chaosConfig, ...req.body }
res.json({ message: 'Chaos config updated', config: chaosConfig })
})
8. Docker Compose Environment for Mock Servers
8.1 docker-compose.yml
version: '3.8'
services:
# json-server mock
json-server:
image: clue/json-server
volumes:
- ./mock-data/db.json:/data/db.json
- ./mock-data/routes.json:/data/routes.json
ports:
- '3001:80'
command: --watch db.json --routes routes.json
healthcheck:
test: ['CMD', 'wget', '-qO-', 'http://localhost/users']
interval: 10s
timeout: 5s
retries: 3
# WireMock server
wiremock:
image: wiremock/wiremock:latest
ports:
- '8080:8080'
volumes:
- ./wiremock/mappings:/home/wiremock/mappings
- ./wiremock/__files:/home/wiremock/__files
command:
- '--verbose'
- '--global-response-templating'
healthcheck:
test: ['CMD', 'wget', '-qO-', 'http://localhost:8080/__admin/health']
interval: 10s
timeout: 5s
retries: 3
# Application (uses mock servers as dependencies)
app:
build: .
ports:
- '8000:8000'
environment:
- USER_SERVICE_URL=http://json-server:80
- PAYMENT_SERVICE_URL=http://wiremock:8080
depends_on:
json-server:
condition: service_healthy
wiremock:
condition: service_healthy
# E2E test runner
e2e-tests:
build:
context: .
dockerfile: Dockerfile.test
environment:
- APP_URL=http://app:8000
depends_on:
- app
command: npm run test:e2e
profiles:
- test
8.2 Test Execution Script
#!/bin/bash
# run-tests.sh
echo "Starting mock server environment..."
docker-compose up -d json-server wiremock
echo "Waiting for health checks..."
docker-compose exec json-server wget -qO- http://localhost/users
docker-compose exec wiremock wget -qO- http://localhost:8080/__admin/health
echo "Starting application..."
docker-compose up -d app
echo "Running E2E tests..."
docker-compose run --rm e2e-tests
echo "Cleaning up..."
docker-compose down
8.3 Mock Data Initialization Script
// scripts/setup-mock-data.ts
const WIREMOCK_URL = 'http://localhost:8080/__admin/mappings'
const stubs = [
{
request: {
method: 'POST',
url: '/api/payment/charge',
},
response: {
status: 200,
headers: { 'Content-Type': 'application/json' },
jsonBody: {
paymentId: 'TEST-PAYMENT-001',
status: 'SUCCESS',
amount: 100,
},
},
},
{
request: {
method: 'POST',
url: '/api/payment/charge',
headers: {
'X-Simulate-Failure': { equalTo: 'true' },
},
},
response: {
status: 402,
headers: { 'Content-Type': 'application/json' },
jsonBody: {
error: 'PAYMENT_DECLINED',
message: 'Card was declined',
},
},
},
]
async function setupMockData() {
for (const stub of stubs) {
const response = await fetch(WIREMOCK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(stub),
})
console.log(`Stub registered: ${response.status}`)
}
}
setupMockData().catch(console.error)
9. Quiz
Quiz 1: What is the key difference between MSW and other mocking approaches like axios-mock-adapter?
Answer: MSW uses a Service Worker to intercept requests at the network level, so it works regardless of which HTTP library the application uses. You can also reuse the same handlers in both the browser and Node.js.
Explanation: Libraries like axios-mock-adapter mock the HTTP client itself, so changing the HTTP client requires rewriting your mock code. MSW intercepts at the actual network layer, meaning it works with fetch, axios, or any other HTTP client transparently. This also makes your mocks more realistic because the full HTTP stack is exercised.
Quiz 2: What is the main limitation of json-server, and how can you overcome it?
Answer: The main limitation of json-server is that it is difficult to implement complex business logic beyond simple CRUD operations.
Explanation: To overcome this, you can use json-server as a library and attach custom routes (server.post('/auth/login', ...)), or write custom middleware to handle more complex logic. For significantly complex scenarios, switching to WireMock or a custom-built mock server is a better approach.
Quiz 3: In what situations is WireMock's Scenarios feature most useful?
Answer: Scenarios are most useful for testing workflows where different responses should be returned based on current state — for example, an order status flow (payment pending → paid → shipping → delivered) or retry logic (first request fails, second succeeds).
Explanation: A standard stub always returns the same response. With Scenarios, each incoming request transitions the state machine to the next state and returns a response appropriate for that state. This is essential for testing non-idempotent APIs and stateful workflows.
Quiz 4: What is the primary advantage of using Prism?
Answer: Prism's main advantage is that it automatically generates a mock server from an OpenAPI spec with no additional mocking code. In Validation mode, it also verifies that real API requests and responses conform to the spec.
Explanation: Prism is particularly valuable in an API-First Design workflow. Once the API spec is defined, the frontend can immediately start development using Prism, and once the backend is complete, Validation mode can automatically verify spec compliance. This eliminates spec drift between documentation and implementation.
Quiz 5: Why should mock servers be combined with Contract Testing?
Answer: Mock servers can drift out of sync with real APIs. Combining with Contract Testing automatically verifies that the mock server matches the real API, preventing bugs caused by mismatches between the mock and the real service.
Explanation: If you develop against a mock server but the real API changes, tests pass in development but fail in production. Tools like Pact let the consumer (frontend) define its expectations as a contract, and the provider (backend) automatically verifies it satisfies those expectations. This creates a safety net that catches integration bugs before deployment.