TL;DR — Mock servers like WireMock (5,000+ GitHub stars), Mockoon, and json-server let mobile teams develop and test without a live backend. This guide covers setup, Android/iOS integration, CI/CD configuration, and advanced patterns like stateful scenarios and error simulation.

Mock servers are critical tools for mobile development, enabling teams to develop and test applications before backend APIs are ready, simulate edge cases, and work offline. According to WireMock’s documentation, WireMock has been downloaded over 15 million times and is used by thousands of teams worldwide. A SmartBear survey on API mocking found that 71% of teams using API mocks reported faster development cycles and fewer integration issues compared to teams without mocking strategies. These tools eliminate the “blocked on backend” problem that delays mobile releases across the industry. Combined with proper API testing strategies, mock servers form the foundation of modern mobile development workflows. This guide covers WireMock, Mockoon, and json-server with Android, iOS, and React Native integration examples.

“Setting up a mock server is one of the first things I do when joining a new mobile project. It unblocks the team immediately and forces a contract-first mindset that pays dividends throughout the entire development cycle.” — Yuri Kan, Senior QA Lead

Why Mock Servers Matter for Mobile Development

Key Benefits

  1. Parallel Development: Frontend and backend teams work independently
  2. Offline Testing: Develop without internet connectivity
  3. Edge Case Simulation: Test error scenarios, rate limiting, timeouts
  4. Consistent Test Data: Reproducible test environments
  5. CI/CD Integration: Fast, reliable automated tests
  6. 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

Comparison Table

FeatureWireMockMockoonjson-serverMSW
Setup ComplexityMediumLowVery LowLow
ConfigurationJava/JSONGUI/JSONJSONJavaScript
Request MatchingAdvancedGoodBasicAdvanced
Response TemplatingHandlebarsHandlebarsStaticDynamic
Proxy ModeYesYesNoYes
Record & ReplayYesNoNoYes
CI IntegrationExcellentGoodExcellentExcellent
Best ForComplex APIsQuick setupPrototypingBrowser-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):

  1. Open Mockoon

  2. Create new environment: “Mobile API Mock”

  3. Set port: 3000

  4. 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:

  1. Set up a mock server for your current project
  2. Integrate with CI/CD pipeline
  3. Create reusable mock configurations
  4. Establish API contract testing workflow

FAQ

What is a mock server in mobile development? A mock server simulates backend API responses so mobile teams can develop and test apps without a real server. It enables parallel development, offline testing, and reproducible test data.

WireMock vs Mockoon vs json-server — which should I use? Use WireMock for complex enterprise APIs with stateful scenarios and CI/CD; Mockoon for quick GUI-based setup and team collaboration; json-server for rapid prototyping and simple CRUD APIs.

How do I integrate a mock server into a CI/CD pipeline? Run WireMock or Mockoon CLI as a Docker service in your GitHub Actions or GitLab CI job, configure the app to use the mock URL, and run tests against it. Mock configs live in version control alongside test fixtures.

Can I use mock servers for Android and iOS tests? Yes. WireMock has an Android library for instrumented tests. For iOS, run WireMock in Docker and point XCTest to localhost:8080. Mockoon CLI works for any platform via npm.

Official Resources

See Also