http_server_mock

Start a mock HTTP server, describe how it should respond to incoming requests with plain Gleam functions, make real HTTP calls against it from your tests, then inspect what it received.

Pass a runtime adapter from http_server_mock_erlang or http_server_mock_js to new/1 to select the underlying server implementation.

import gleam/http
import gleam/http/request
import gleam/http/response
import gleam/list
import http_server_mock
import http_server_mock_erlang

pub fn my_test() {
  use server <- http_server_mock.with_handler(
    http_server_mock.new(http_server_mock_erlang.server()),
    fn(req) {
      case req.method, request.path_segments(req) {
        http.Get, ["greet"] ->
          response.new(200) |> response.set_body("hello") |> Ok
        _ -> Error(http_server_mock.UnexpectedRequest(req))
      }
    },
  )

  let url = http_server_mock.base_url(server) <> "/greet"
  // ... make a real HTTP call against `url` ...

  assert list.length(http_server_mock.received(server)) == 1
}

Types

Configuration for a mock server, built with new and with_port.

pub opaque type Config

An opaque handle to a running mock server. Only meaningful while the server is running: once stop has been called (or the use scope that started it has returned), don’t use the value again.

pub type MockServer =
  @internal MockServer

Re-exported for use in type annotations by runtime packages and users. Constructed by a runtime package (e.g. http_server_mock_erlang.server()) and passed to new.

pub type ServerAdapter =
  @internal ServerAdapter

A single routing rule: matches decides whether it applies to an incoming request, and it’s paired with a fixed Response(String) to send back when it does. matches is an ordinary Gleam function written with case/pattern matching over gleam/http/request helpers.

pub type Stub =
  @internal Stub

Wrapped in Error and returned by a with_handler handler when it had no case for the incoming request.

pub type UnexpectedRequest {
  UnexpectedRequest(request: request.Request(String))
}

Constructors

Values

pub fn add_stub(
  mock_server: MockServer,
  stub: Stub,
) -> Result(Nil, String)

Registers an additional stub with a running server.

pub fn base_url(mock_server: MockServer) -> String

Returns the base URL of the running server, e.g. "http://localhost:54321".

pub fn new(adapter: ServerAdapter) -> Config

Creates a default configuration using the given runtime adapter, e.g. http_server_mock_erlang.server().

The server binds to a free port chosen by the operating system unless overridden with with_port. This is the default so that concurrent tests never clash over a port.

pub fn received(
  mock_server: MockServer,
) -> List(request.Request(String))

Returns every request the server has received since it started (or since the last reset_requests/reset), in the order they arrived.

Write your own assertions against the result, e.g. assert list.length(http_server_mock.received(server)) == 1.

pub fn received_by(
  mock_server: MockServer,
  stub: Stub,
) -> List(request.Request(String))

Returns every request that matched the given stub, in the order they arrived. Pass the exact Stub value you registered, the same one remove_stub expects - a freshly-built stub with equivalent-looking logic is never considered equal to it, even if it behaves the same.

This saves re-writing a stub’s matches predicate a second time in the test body just to filter received:

let ping = http_server_mock.stub(fn(req) { req.path == "/ping" }, response.new(200))
use server <- http_server_mock.with_stubs(config, [ping])
// ...
assert list.length(http_server_mock.received_by(server, ping)) == 1
pub fn remove_stub(mock_server: MockServer, stub: Stub) -> Nil

Removes a specific stub from a running server. Pass the exact Stub value you originally registered (the one returned by stub) - not a freshly-built stub with equivalent-looking logic, since two separately-created stubs are never considered equal even if they behave the same. Has no effect if that stub isn’t currently registered.

pub fn reset(mock_server: MockServer) -> Nil

Removes all stubs and clears the recorded request history in one call.

pub fn reset_requests(mock_server: MockServer) -> Nil

Clears the server’s recorded request history.

pub fn reset_stubs(mock_server: MockServer) -> Nil

Removes all registered stubs. Requests made after this call return 404 until new stubs are registered.

pub fn start(config: Config) -> Result(MockServer, String)

Starts a server with no initial stubs. Prefer with_stubs/with_handler unless your test can’t be structured as a use block (for example, gleeunit setup/teardown pairs). You are responsible for calling stop yourself.

pub fn start_with_stubs(
  config: Config,
  stubs: List(Stub),
) -> Result(MockServer, String)

Like start, but with an initial stub list.

pub fn stop(mock_server: MockServer) -> Nil

Stops the mock server and releases the port it was bound to.

If the code running inside a with_handler/with_stubs block panics before returning, stop is not guaranteed to run. The server process is only cleaned up when the test runner process exits.

pub fn stub(
  matches: fn(request.Request(String)) -> Bool,
  response: response.Response(String),
) -> Stub

Creates a stub: matches decides whether it applies to a request, response is what to send back when it does.

A stub’s response is a fixed value, not computed from the request: the test that builds the stub already controls every value that ends up in the request it’s matching, so it already has everything it needs to build the response too. Different responses for different inputs are different stubs with narrower matches predicates, not one stub branching internally.

When more than one registered stub matches the same request, the one registered first wins.

pub fn unmatched_requests(
  mock_server: MockServer,
) -> List(request.Request(String))

Returns every request the server received that did not match any stub (or that a with_handler handler had no case for). Useful for diagnosing why an expected response never came back: if a request you expected to be handled shows up here, check your matches/handler logic.

pub fn with_handler(
  config: Config,
  handler: fn(request.Request(String)) -> Result(
    response.Response(String),
    UnexpectedRequest,
  ),
  callback: fn(MockServer) -> a,
) -> a

Starts a server whose behaviour is entirely described by handler. callback receives the running server and is everything that runs while it’s up - with the use syntax below, that’s the rest of the enclosing function. The server is only stopped once callback returns, right before this function hands back its result:

pub fn my_test() {
  use server <- http_server_mock.with_handler(config, fn(req) {
    case req.method, request.path_segments(req) {
      http.Get, ["greet"] -> response.new(200) |> response.set_body("hi") |> Ok
      _ -> Error(http_server_mock.UnexpectedRequest(req))
    }
  })

  // Everything from here to the end of this function is `callback` - the
  // server is running for all of it, so this is where you make requests
  // and assert on the results.
  let response = my_http_client.get(http_server_mock.base_url(server) <> "/greet")
  assert response.body == "hi"

  // The server stops right here, as this function returns - not before.
}
pub fn with_port(config: Config, port: Int) -> Config

Overrides the port the server will bind to. Prefer the default in tests.

pub fn with_stubs(
  config: Config,
  stubs: List(Stub),
  callback: fn(MockServer) -> a,
) -> a

Starts a server with a fixed initial stub list. callback receives the running server and is everything that runs while it’s up - with the use syntax below, that’s the rest of the enclosing function. The server is only stopped once callback returns, right before this function hands back its result:

pub fn my_test() {
  let ping =
    http_server_mock.stub(
      fn(req) { req.path == "/ping" },
      response.new(200) |> response.set_body("pong"),
    )
  use server <- http_server_mock.with_stubs(config, [ping])

  // Everything from here to the end of this function is `callback` - the
  // server is running for all of it, so this is where you make requests
  // and assert on the results.
  let response = my_http_client.get(http_server_mock.base_url(server) <> "/ping")
  assert response.body == "pong"

  // The server stops right here, as this function returns - not before.
}
Search Document