Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MockServer

The MockServer module provides a pre-configured MockServer container for mocking HTTP/HTTPS services in integration tests.

Quick Start

open Testcontainers_mockserver

let test_mockserver () =
  Mockserver_container.with_mockserver (fun container url ->
    Printf.printf "MockServer running at: %s\n" url;
    Lwt.return_unit
  )

Installation

opam install testcontainers-mockserver

In your dune file:

(libraries testcontainers-mockserver)

Configuration

Basic Usage

Mockserver_container.with_mockserver (fun container url ->
  (* url: "http://127.0.0.1:1080" *)
  ...
)

Configuration Options

FunctionDefaultDescription
with_imagemockserver/mockserver:5.15.0Docker image
with_log_levelINFOLog level (INFO, DEBUG, TRACE, WARN, ERROR)
with_max_expectationsNoneMaximum number of expectations

Custom Configuration

Mockserver_container.with_mockserver
  ~config:(fun c -> c
    |> Mockserver_container.with_log_level "DEBUG"
    |> Mockserver_container.with_max_expectations 100)
  (fun container url -> ...)

Connection Details

HTTP URL

http://127.0.0.1:1080

Individual Components

Mockserver_container.with_mockserver (fun container url ->
  let* host = Mockserver_container.host container in  (* "127.0.0.1" *)
  let* port = Mockserver_container.port config container in  (* 1080 *)
  ...
)

Manual Lifecycle

let run_tests () =
  let config =
    Mockserver_container.create ()
    |> Mockserver_container.with_log_level "DEBUG"
  in
  let* container = Mockserver_container.start config in
  let* url = Mockserver_container.url config container in

  (* Run tests... *)

  let* () = Testcontainers.Container.terminate container in
  Lwt.return_unit

Creating Expectations

Simple Expectation

let create_expectation container =
  let* (exit_code, _) = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/expectation";
    "-H"; "Content-Type: application/json";
    "-d"; {|{
      "httpRequest": {
        "method": "GET",
        "path": "/api/hello"
      },
      "httpResponse": {
        "statusCode": 200,
        "body": "Hello World!"
      }
    }|}
  ] in
  Lwt.return (exit_code = 0)

Expectation with Headers

let create_expectation_with_headers container =
  let* (exit_code, _) = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/expectation";
    "-H"; "Content-Type: application/json";
    "-d"; {|{
      "httpRequest": {
        "method": "GET",
        "path": "/api/user",
        "headers": {
          "Authorization": ["Bearer token123"]
        }
      },
      "httpResponse": {
        "statusCode": 200,
        "headers": {
          "Content-Type": ["application/json"]
        },
        "body": "{\"name\": \"Alice\", \"id\": 1}"
      }
    }|}
  ] in
  Lwt.return (exit_code = 0)

POST Request Expectation

let create_post_expectation container =
  let* (exit_code, _) = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/expectation";
    "-H"; "Content-Type: application/json";
    "-d"; {|{
      "httpRequest": {
        "method": "POST",
        "path": "/api/users",
        "body": {
          "type": "JSON",
          "json": {"name": "Bob"}
        }
      },
      "httpResponse": {
        "statusCode": 201,
        "body": "{\"id\": 2, \"name\": \"Bob\"}"
      }
    }|}
  ] in
  Lwt.return (exit_code = 0)

Testing the Mock

Make a Request

let test_mock container =
  (* First create the expectation *)
  let* _ = create_expectation container in

  (* Then test it *)
  let* (exit_code, output) = Testcontainers.Container.exec container [
    "curl"; "-s"; "http://localhost:1080/api/hello"
  ] in
  Printf.printf "Response: %s\n" output;
  Lwt.return (exit_code = 0 && output = "Hello World!")

Verification

Verify Request Was Made

let verify_request container =
  let* (exit_code, output) = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/verify";
    "-H"; "Content-Type: application/json";
    "-d"; {|{
      "httpRequest": {
        "method": "GET",
        "path": "/api/hello"
      },
      "times": {
        "atLeast": 1
      }
    }|}
  ] in
  Lwt.return (exit_code = 0)

Clearing Expectations

Clear All Expectations

let clear_all container =
  let* (exit_code, _) = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/clear"
  ] in
  Lwt.return (exit_code = 0)

Reset MockServer

let reset container =
  let* (exit_code, _) = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/reset"
  ] in
  Lwt.return (exit_code = 0)

Complete Test Example

open Lwt.Syntax
open Testcontainers
open Testcontainers_mockserver

let test_api_mock _switch () =
  Mockserver_container.with_mockserver (fun container url ->
    (* Create expectation *)
    let* (code, _) = Container.exec container [
      "curl"; "-s"; "-X"; "PUT";
      "http://localhost:1080/mockserver/expectation";
      "-H"; "Content-Type: application/json";
      "-d"; {|{
        "httpRequest": {"path": "/api/users/1"},
        "httpResponse": {
          "statusCode": 200,
          "body": "{\"id\": 1, \"name\": \"Alice\"}"
        }
      }|}
    ] in
    Alcotest.(check int) "expectation created" 0 code;

    (* Test the mock *)
    let* (code, output) = Container.exec container [
      "curl"; "-s"; "http://localhost:1080/api/users/1"
    ] in
    Alcotest.(check int) "request succeeds" 0 code;
    Alcotest.(check bool) "has response" true
      (String.length output > 0);

    (* Verify the request was made *)
    let* (code, _) = Container.exec container [
      "curl"; "-s"; "-X"; "PUT";
      "http://localhost:1080/mockserver/verify";
      "-H"; "Content-Type: application/json";
      "-d"; {|{
        "httpRequest": {"path": "/api/users/1"},
        "times": {"atLeast": 1}
      }|}
    ] in
    Alcotest.(check int) "verification passes" 0 code;

    Lwt.return_unit
  )

let () =
  Lwt_main.run (
    Alcotest_lwt.run "MockServer Tests" [
      "api", [
        Alcotest_lwt.test_case "mock endpoints" `Slow test_api_mock;
      ];
    ]
  )

Wait Strategy

MockServer uses a log-based wait strategy:

Wait_strategy.for_log ~timeout:60.0 "started on port"

Use Cases

Testing External API Dependencies

Mock external APIs your application depends on:

(* Mock a payment gateway *)
let mock_payment_api container =
  let* _ = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/expectation";
    "-H"; "Content-Type: application/json";
    "-d"; {|{
      "httpRequest": {
        "method": "POST",
        "path": "/v1/charges"
      },
      "httpResponse": {
        "statusCode": 200,
        "body": "{\"id\": \"ch_123\", \"status\": \"succeeded\"}"
      }
    }|}
  ] in
  Lwt.return_unit

Testing Error Handling

(* Mock error responses *)
let mock_error_response container =
  let* _ = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/expectation";
    "-H"; "Content-Type: application/json";
    "-d"; {|{
      "httpRequest": {"path": "/api/fail"},
      "httpResponse": {
        "statusCode": 500,
        "body": "{\"error\": \"Internal Server Error\"}"
      }
    }|}
  ] in
  Lwt.return_unit

Testing Timeouts

(* Mock slow response *)
let mock_slow_response container =
  let* _ = Testcontainers.Container.exec container [
    "curl"; "-s"; "-X"; "PUT";
    "http://localhost:1080/mockserver/expectation";
    "-H"; "Content-Type: application/json";
    "-d"; {|{
      "httpRequest": {"path": "/api/slow"},
      "httpResponse": {
        "statusCode": 200,
        "delay": {"timeUnit": "SECONDS", "value": 5}
      }
    }|}
  ] in
  Lwt.return_unit

Troubleshooting

Expectation Not Matching

Check that the request matches exactly:

  • HTTP method
  • Path
  • Headers (if specified)
  • Body (if specified)

Enable DEBUG logging for more details:

Mockserver_container.with_log_level "DEBUG"

Connection Refused

Always use with_mockserver which waits for the server to be ready:

(* Good *)
Mockserver_container.with_mockserver (fun container url -> ...)

Multiple Expectations

MockServer matches expectations in order. Put more specific expectations before general ones.