Mock servers are essential tools for mobile development, enabling teams to develop and test applications before backend APIs are ready, simulate edge cases, and work offline. Combined with proper API testing strategies, mock servers form the foundation of modern mobile development workflows. This guide covers the most popular mock server solutions and their practical implementation.
Why Mock Servers Matter for Mobile Development
Key Benefits
- Parallel Development: Frontend and backend teams work independently
- Offline Testing: Develop without internet connectivity
- Edge Case Simulation: Test error scenarios, rate limiting, timeouts
- Consistent Test Data: Reproducible test environments
- CI/CD Integration: Fast, reliable automated tests
- Cost Reduction: No backend infrastructure needed for local development
Real-World Scenario
Without Mock Server:
- Backend API delayed by 3 weeks
- Mobile team blocked
- Integration issues discovered late
- Testing depends on shared staging environment
With Mock Server:
- Mobile team starts immediately
- Contract defined upfront
- Edge cases tested early
- Parallel CI pipelines
- Integration issues caught during development
Popular Mock Server Solutions
Comparison Table
Feature | WireMock | Mockoon | json-server | MSW |
---|---|---|---|---|
Setup Complexity | Medium | Low | Very Low | Low |
Configuration | Java/JSON | GUI/JSON | JSON | JavaScript |
Request Matching | Advanced | Good | Basic | Advanced |
Response Templating | Handlebars | Handlebars | Static | Dynamic |
Proxy Mode | Yes | Yes | No | Yes |
Record & Replay | Yes | No | No | Yes |
CI Integration | Excellent | Good | Excellent | Excellent |
Best For | Complex APIs | Quick setup | Prototyping | Browser-based apps |
WireMock: The Enterprise Solution
Installation and Setup
Docker (recommended for mobile CI/CD):
# Run WireMock container
docker run -d \
--name wiremock \
-p 8080:8080 \
-v $(pwd)/wiremock:/home/wiremock \
wiremock/wiremock:latest
Standalone JAR:
# Download WireMock
wget https://repo1.maven.org/maven2/org/wiremock/wiremock-standalone/3.3.1/wiremock-standalone-3.3.1.jar
# Run WireMock
java -jar wiremock-standalone-3.3.1.jar --port 8080
Configuration Examples
Basic Stub (wiremock/mappings/get-user.json):
{
"request": {
"method": "GET",
"urlPathPattern": "/api/users/([0-9]+)"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"id": "{{request.pathSegments.[2]}}",
"username": "john_doe",
"email": "john@example.com",
"avatar": "https://cdn.example.com/avatar.jpg",
"createdAt": "2024-01-15T10:30:00Z"
},
"transformers": ["response-template"]
}
}
Advanced Response Templating:
{
"request": {
"method": "POST",
"url": "/api/orders",
"headers": {
"Content-Type": {
"equalTo": "application/json"
}
}
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json",
"Location": "/api/orders/{{randomValue type='UUID'}}"
},
"jsonBody": {
"orderId": "{{randomValue type='UUID'}}",
"status": "pending",
"createdAt": "{{now format='yyyy-MM-dd HH:mm:ss'}}",
"items": "{{jsonPath request.body '$.items'}}",
"total": "{{jsonPath request.body '$.total'}}"
},
"transformers": ["response-template"]
}
}
Error Simulation:
{
"scenarioName": "Rate Limiting",
"requiredScenarioState": "Started",
"newScenarioState": "Rate Limited",
"request": {
"method": "GET",
"url": "/api/products"
},
"response": {
"status": 429,
"headers": {
"Content-Type": "application/json",
"Retry-After": "60"
},
"jsonBody": {
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"retryAfter": 60
}
}
}
Network Delay Simulation:
{
"request": {
"method": "GET",
"url": "/api/slow-endpoint"
},
"response": {
"status": 200,
"fixedDelayMilliseconds": 3000,
"jsonBody": {
"message": "This response was delayed by 3 seconds"
}
}
}
Android Integration
// app/build.gradle.kts
dependencies {
// WireMock for instrumented tests
androidTestImplementation("com.github.tomakehurst:wiremock-jre8:2.35.0")
}
// AndroidTest setup
class WireMockTest {
private lateinit var wireMockServer: WireMockServer
@Before
fun setup() {
wireMockServer = WireMockServer(
WireMockConfiguration.options()
.port(8080)
.notifier(ConsoleNotifier(true))
)
wireMockServer.start()
WireMock.configureFor("localhost", 8080)
}
@After
fun tearDown() {
wireMockServer.stop()
}
@Test
fun testUserFetch() {
// Setup stub
stubFor(
get(urlEqualTo("/api/users/123"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("""
{
"id": 123,
"username": "test_user",
"email": "test@example.com"
}
""".trimIndent())
)
)
// Configure app to use WireMock URL
val apiClient = ApiClient("http://localhost:8080")
// Run test
runBlocking {
val user = apiClient.getUser(123)
assertEquals("test_user", user.username)
}
// Verify request was made
verify(
getRequestedFor(urlEqualTo("/api/users/123"))
.withHeader("Accept", equalTo("application/json"))
)
}
@Test
fun testNetworkTimeout() {
stubFor(
get(urlEqualTo("/api/users/123"))
.willReturn(
aResponse()
.withFixedDelay(5000) // 5 second delay
)
)
val apiClient = ApiClient("http://localhost:8080")
assertThrows<SocketTimeoutException> {
runBlocking {
apiClient.getUser(123)
}
}
}
}
iOS Integration
Modern iOS and Android testing in 2025 increasingly relies on mock servers for fast, reliable test execution. Here’s how to integrate WireMock with iOS tests:
// Tests/MockServerTests.swift
import XCTest
@testable import YourApp
class WireMockTests: XCTestCase {
let baseURL = "http://localhost:8080"
override func setUp() {
super.setUp()
// Ensure WireMock is running (via Docker or CI script)
setupMockStubs()
}
override func tearDown() {
resetWireMock()
super.tearDown()
}
func setupMockStubs() {
let stubURL = URL(string: "\(baseURL)/__admin/mappings")!
var request = URLRequest(url: stubURL)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let stub = """
{
"request": {
"method": "GET",
"url": "/api/users/123"
},
"response": {
"status": 200,
"jsonBody": {
"id": 123,
"username": "test_user"
}
}
}
"""
request.httpBody = stub.data(using: .utf8)
let expectation = expectation(description: "Setup stub")
URLSession.shared.dataTask(with: request) { _, _, _ in
expectation.fulfill()
}.resume()
wait(for: [expectation], timeout: 5.0)
}
func resetWireMock() {
let resetURL = URL(string: "\(baseURL)/__admin/mappings/reset")!
var request = URLRequest(url: resetURL)
request.httpMethod = "POST"
URLSession.shared.dataTask(with: request).resume()
}
func testFetchUser() async throws {
let apiClient = APIClient(baseURL: baseURL)
let user = try await apiClient.fetchUser(id: 123)
XCTAssertEqual(user.id, 123)
XCTAssertEqual(user.username, "test_user")
}
}
Mockoon: GUI-First Approach
Installation
# macOS
brew install --cask mockoon
# Or download from https://mockoon.com
Configuration
Creating a Mock API (via GUI):
- Open Mockoon
- Create new environment: “Mobile API Mock”
- Set port: 3000
- Add routes:
- GET /api/users → Response with users array
- POST /api/auth/login → Return JWT token
- GET /api/products → Paginated products
Programmatic Configuration (mockoon-data.json):
{
"uuid": "mobile-api-mock",
"name": "Mobile API Mock",
"port": 3000,
"routes": [
{
"uuid": "route-1",
"method": "get",
"endpoint": "api/users/:id",
"responses": [
{
"uuid": "response-1",
"body": "{\n \"id\": \"{{urlParam 'id'}}\",\n \"username\": \"{{faker 'internet.userName'}}\",\n \"email\": \"{{faker 'internet.email'}}\",\n \"avatar\": \"{{faker 'image.avatar'}}\"\n}",
"statusCode": 200,
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
]
}
]
},
{
"uuid": "route-2",
"method": "post",
"endpoint": "api/auth/login",
"responses": [
{
"uuid": "response-2",
"body": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.{{faker 'random.alphaNumeric' 50}}\",\n \"expiresIn\": 3600,\n \"user\": {\n \"id\": \"{{faker 'random.uuid'}}\",\n \"email\": \"{{body 'email'}}\"\n }\n}",
"statusCode": 200
}
]
}
]
}
CLI Usage (for CI/CD):
# Install Mockoon CLI
npm install -g @mockoon/cli
# Run mock server
mockoon-cli start --data ./mockoon-data.json --port 3000
# Run in background
mockoon-cli start --data ./mockoon-data.json --daemon-name mobile-mock
React Native Integration
// __tests__/api.test.js
import { setupServer } from '@mockoon/cli';
describe('API Tests', () => {
let mockServer;
beforeAll(async () => {
mockServer = await setupServer({
data: './mockoon-data.json',
port: 3000,
});
await mockServer.start();
});
afterAll(async () => {
await mockServer.stop();
});
test('should fetch user data', async () => {
const response = await fetch('http://localhost:3000/api/users/123');
const data = await response.json();
expect(data.id).toBe('123');
expect(data).toHaveProperty('username');
expect(data).toHaveProperty('email');
});
});
json-server: Rapid Prototyping
Installation and Setup
# Install globally
npm install -g json-server
# Or as dev dependency
npm install --save-dev json-server
Create Mock Database (db.json):
{
"users": [
{
"id": 1,
"username": "john_doe",
"email": "john@example.com",
"role": "admin"
},
{
"id": 2,
"username": "jane_smith",
"email": "jane@example.com",
"role": "user"
}
],
"posts": [
{
"id": 1,
"userId": 1,
"title": "First Post",
"content": "Hello world!",
"createdAt": "2024-01-15T10:00:00Z"
}
],
"comments": [
{
"id": 1,
"postId": 1,
"userId": 2,
"text": "Great post!",
"createdAt": "2024-01-15T11:00:00Z"
}
]
}
Custom Routes (routes.json):
{
"/api/*": "/$1",
"/api/users/:id/posts": "/posts?userId=:id",
"/api/posts/:id/comments": "/comments?postId=:id"
}
Run Server:
# Basic
json-server --watch db.json --port 3000
# With custom routes
json-server --watch db.json --routes routes.json --port 3000
# With delay simulation
json-server --watch db.json --delay 1000
Package.json Scripts:
{
"scripts": {
"mock-api": "json-server --watch db.json --port 3000",
"mock-api:slow": "json-server --watch db.json --port 3000 --delay 2000"
}
}
Flutter Integration
// test/widget_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() {
// Start json-server before tests
// npm run mock-api
const baseUrl = 'http://localhost:3000';
group('User API Tests', () {
test('fetch users', () async {
final response = await http.get(Uri.parse('$baseUrl/users'));
expect(response.statusCode, 200);
final List users = json.decode(response.body);
expect(users.length, greaterThan(0));
expect(users[0], containsPair('username', isNotNull));
});
test('create user', () async {
final newUser = {
'username': 'test_user',
'email': 'test@example.com',
'role': 'user'
};
final response = await http.post(
Uri.parse('$baseUrl/users'),
headers: {'Content-Type': 'application/json'},
body: json.encode(newUser),
);
expect(response.statusCode, 201);
final created = json.decode(response.body);
expect(created['id'], isNotNull);
expect(created['username'], 'test_user');
});
test('filter posts by user', () async {
final response = await http.get(
Uri.parse('$baseUrl/posts?userId=1')
);
expect(response.statusCode, 200);
final List posts = json.decode(response.body);
expect(posts.every((post) => post['userId'] == 1), true);
});
});
}
CI/CD Integration
Integrating mock servers into CI/CD pipelines enables fast, reliable automated testing. For Java-based API testing frameworks like REST Assured, WireMock provides seamless integration with minimal configuration.
GitHub Actions with WireMock
# .github/workflows/mobile-tests.yml
name: Mobile Tests
on: [push, pull_request]
jobs:
android-tests:
runs-on: ubuntu-latest
services:
wiremock:
image: wiremock/wiremock:latest
ports:
- 8080:8080
volumes:
- ${{ github.workspace }}/wiremock:/home/wiremock
steps:
- uses: actions/checkout@v3
- name: Setup WireMock stubs
run: |
cp -r test-fixtures/wiremock/* wiremock/
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Run Android Tests
run: |
./gradlew connectedAndroidTest \
-Dapi.base.url=http://localhost:8080
- name: Upload Test Results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: app/build/reports/androidTests/
GitLab CI with Mockoon
# .gitlab-ci.yml
test:mobile:
image: node:18
services:
- name: mockoon/cli:latest
alias: mock-api
command: ["start", "--data", "/data/mockoon-config.json", "--port", "3000"]
before_script:
- npm install -g @mockoon/cli
- mockoon-cli start --data ./mockoon-config.json --daemon-name test-mock
script:
- npm run test:mobile -- --api-url=http://mock-api:3000
after_script:
- mockoon-cli stop test-mock
Advanced Patterns
Dynamic Response Based on Request
WireMock with Request Matching:
{
"request": {
"method": "POST",
"url": "/api/login",
"bodyPatterns": [
{
"matchesJsonPath": "$[?(@.email == 'admin@example.com')]"
}
]
},
"response": {
"status": 200,
"jsonBody": {
"token": "admin-token-xxx",
"role": "admin"
}
}
},
{
"request": {
"method": "POST",
"url": "/api/login",
"bodyPatterns": [
{
"matchesJsonPath": "$[?(@.email =~ /.*@example.com/)]"
}
]
},
"response": {
"status": 200,
"jsonBody": {
"token": "user-token-xxx",
"role": "user"
}
}
}
Stateful Scenarios
WireMock State Machine:
{
"scenarioName": "Shopping Cart",
"requiredScenarioState": "Started",
"newScenarioState": "Item Added",
"request": {
"method": "POST",
"url": "/api/cart/items"
},
"response": {
"status": 201,
"jsonBody": {
"cartId": "cart-123",
"itemCount": 1
}
}
},
{
"scenarioName": "Shopping Cart",
"requiredScenarioState": "Item Added",
"request": {
"method": "GET",
"url": "/api/cart"
},
"response": {
"status": 200,
"jsonBody": {
"cartId": "cart-123",
"items": [
{"productId": "{{request.body}}", "quantity": 1}
]
}
}
}
Best Practices
1. Contract-First Development
Define API contracts (OpenAPI/Swagger) before implementation. Generate mock stubs from contracts. This approach aligns perfectly with API contract testing for mobile applications, ensuring frontend and backend teams stay synchronized.
2. Version Control Mock Configs
project/
├── wiremock/
│ ├── mappings/
│ │ ├── users.json
│ │ └── products.json
│ └── __files/
│ └── large-response.json
└── .gitignore (exclude __files for large files)
3. Environment-Specific Configurations
// Android BuildConfig
android {
buildTypes {
debug {
buildConfigField("String", "API_BASE_URL", "\"http://localhost:8080\"")
}
release {
buildConfigField("String", "API_BASE_URL", "\"https://api.production.com\"")
}
}
}
4. Realistic Data with Faker
Use Mockoon’s Faker.js integration or WireMock response templating:
{
"username": "{{faker 'internet.userName'}}",
"avatar": "{{faker 'image.avatar'}}",
"createdAt": "{{faker 'date.past'}}"
}
Conclusion
Mock servers are indispensable for modern mobile development:
- WireMock: Best for complex enterprise APIs, CI/CD, contract testing
- Mockoon: Ideal for rapid setup, GUI-based workflow, team collaboration
- json-server: Perfect for prototyping, simple CRUD APIs, quick demos
Choose based on your team’s needs, existing infrastructure, and complexity requirements. Combine multiple solutions when appropriate—use json-server for prototyping, then migrate to WireMock for production-grade testing.
Next Steps:
- Set up a mock server for your current project
- Integrate with CI/CD pipeline
- Create reusable mock configurations
- Establish API contract testing workflow