A 100% Elixir, 0-dependency mock HTTP server for testing HTTP clients.
Passby provides a clean, drop-in replacement for Bypass without requiring
Plug, Cowboy, or Ranch.
Basic Usage
test "fetches user profile" do
bypass = Passby.open()
Passby.expect_once(bypass, "GET", "/api/users/42", fn conn ->
Passby.Conn.put_resp_header(conn, "content-type", "application/json")
Passby.Conn.resp(conn, 200, ~s({"id": 42, "name": "Alice"}))
end)
assert {:ok, %{"name" => "Alice"}} = MyClient.get_user("#{bypass.url}/api/users/42")
endPlug and Bypass Compatibility
Passby.Conn implements the same attributes and helpers as Plug.Conn
(get_req_header/2, put_resp_header/3, resp/3, send_resp/1,
fetch_query_params/1), allowing existing test suites to migrate from
Bypass with zero friction.
Handlers receive a conn whose query_params and params are already decoded
the same way Bypass/Plug.Conn.Query decode them, including bracket
notation ("filter[name]=Manuel" -> %{"filter" => %{"name" => "Manuel"}})
and lists ("tags[]=a&tags[]=b" -> %{"tags" => ["a", "b"]}).
Expectation paths may use :param segments ("/users/:id"); captured values
land in conn.path_params and are merged into conn.params.
Summary
Functions
Closes the listening socket to simulate network downtime or connection refused errors.
Adds an expectation that will be called for any incoming request.
Adds an expectation for a specific HTTP method and path.
Adds an expectation that will be called at most once for any request.
Adds an expectation that will be called at most once for a specific method and path.
Fetches query params into the connection. Delegate to Passby.Conn.fetch_query_params/2.
Gets request headers from the connection. Delegate to Passby.Conn.get_req_header/2.
Starts a new Passby mock HTTP server instance.
Clears all expectations and stubs configured on the Passby instance.
Sets a response header. Delegate to Passby.Conn.put_resp_header/3.
Sets the response status and body. Delegate to Passby.Conn.resp/3.
Sends the configured response. Delegate to Passby.Conn.send_resp/1.
Sends the response immediately. Delegate to Passby.Conn.send_resp/3.
Adds a fallback stub handler that matches if no specific expectation matched.
Reopens the listening socket after being closed with down/1.
Returns the base URL of the Passby instance.
Returns the URL of the Passby instance with the given path appended.
Types
@type t() :: %Passby{pid: pid(), port: :inet.port_number(), url: String.t()}
Functions
@spec down(t()) :: :ok
Closes the listening socket to simulate network downtime or connection refused errors.
@spec expect(t(), (Passby.Conn.t() -> any())) :: :ok
Adds an expectation that will be called for any incoming request.
Examples
Passby.expect(bypass, fn conn ->
Passby.Conn.resp(conn, 200, "OK")
end)
Adds an expectation for a specific HTTP method and path.
The path may contain :param segments; the captured values are placed in
conn.path_params and merged into conn.params (path params win over query
params on key conflicts), matching Bypass.
Examples
Passby.expect(bypass, "POST", "/api/v1/checkout", fn conn ->
Passby.Conn.resp(conn, 201, "Created")
end)
Passby.expect(bypass, "GET", "/users/:id", fn conn ->
Passby.Conn.resp(conn, 200, ~s({"id": #{conn.params["id"]}}))
end)
@spec expect_once(t(), (Passby.Conn.t() -> any())) :: :ok
Adds an expectation that will be called at most once for any request.
@spec expect_once( t(), String.t() | atom() | nil, String.t() | nil, (Passby.Conn.t() -> any()) ) :: :ok
Adds an expectation that will be called at most once for a specific method and path.
Examples
Passby.expect_once(bypass, "DELETE", "/sessions/current", fn conn ->
Passby.Conn.resp(conn, 204, "")
end)
Fetches query params into the connection. Delegate to Passby.Conn.fetch_query_params/2.
Gets request headers from the connection. Delegate to Passby.Conn.get_req_header/2.
Starts a new Passby mock HTTP server instance.
Options
:port- The port number to listen on (default0for dynamic ephemeral port).:bind_address- The IP address tuple to bind to (default{127, 0, 0, 1}).
Examples
iex> bypass = Passby.open()
iex> is_integer(bypass.port)
true
iex> is_binary(bypass.url)
true
iex> bypass.url =~ "http://127.0.0.1:"
true
@spec pass(t()) :: :ok
Clears all expectations and stubs configured on the Passby instance.
Sets a response header. Delegate to Passby.Conn.put_resp_header/3.
Sets the response status and body. Delegate to Passby.Conn.resp/3.
Sends the configured response. Delegate to Passby.Conn.send_resp/1.
Sends the response immediately. Delegate to Passby.Conn.send_resp/3.
Adds a fallback stub handler that matches if no specific expectation matched.
@spec up(t()) :: :ok
Reopens the listening socket after being closed with down/1.
Returns the base URL of the Passby instance.
Examples
iex> bypass = Passby.open()
iex> Passby.url(bypass) == bypass.url
true
Returns the URL of the Passby instance with the given path appended.
Examples
iex> bypass = Passby.open()
iex> Passby.url(bypass, "/api/users/42") == "#{bypass.url}/api/users/42"
true
iex> Passby.url(bypass, "api/users/42") == "#{bypass.url}/api/users/42"
true