Passby - 100% Elixir, 0-dependency mock HTTP server (Bypass drop-in replacement)

I created Passby, a mock HTTP server written 100% in Elixir with 0 runtime dependencies.

It is designed as a lightweight drop-in replacement for Bypass, keeping the same API and semantics without pulling in plug_cowboy, cowboy, cowlib, or ranch into your test environment.

Quick Example

test "fetches user profile" do
  bypass = Passby.open()

  Passby.expect_once(bypass, "GET", "/api/users/42", fn conn ->
    Passby.Conn.resp(conn, 200, ~s({"id": 42, "name": "Alice"}))
  end)

  url = "http://127.0.0.1:#{bypass.port}/api/users/42"
  assert {:ok, %{"name" => "Alice"}} = MyClient.get_user(url)
end

Links

• Hex.pm: passby | Hex
• HexDocs: passby v0.1.0 — Documentation
• GitHub:

Feedback and contributions are welcome!

14 Likes

This is great! The only thing I’d additionally consider is maybe adding a convenience struct.url field:

- url = "http://127.0.0.1:#{bypass.port}/api/users/42"
- assert {:ok, %{"name" => "Alice"}} = MyClient.get_user(url)
+ assert {:ok, %{"name" => "Alice"}} = MyClient.get_user("#{bypass.url}/api/users/42")

or having a separate some kind of %{url: url} = Passby.start(). In any case, awesome idea with (essentially) zero dependency replacement!

Edit: oh, it truly has zero dependencies as you’re re-implementing most commonly used Plug API. That’s interesting!

3 Likes

Very good! I usually prefer to have a real server to test network-touching code. It’s a welcome addition to the ecosystem.

2 Likes

I’ve just added this in passby v0.1.1 while keeping 100% backward compatibility with Bypass:

Now %Passby{} includes the `.url` field, and there’s also a helper Passby.url/1,2:

# 1. Using the struct field:
assert {:ok, %{"name" => "Alice"}} = MyClient.get_user("#{bypass.url}/api/users/42")

# 2. Or using the Passby.url/2 helper (handles leading slashes automatically):
assert {:ok, %{"name" => "Alice"}} = MyClient.get_user(Passby.url(bypass, "/api/users/42"))

# 3. Existing Bypass-style code using .port continues to work unchanged:
assert {:ok, %{"name" => "Alice"}} = MyClient.get_user("http://127.0.0.1:#{bypass.port}/api/users/42")

Regarding the zero dependencies, yes, exactly! It relies entirely on standard Erlang/OTP (:gen_tcp, :inet, and :erlang.decode_packet) and provides a lightweight Passby.Conn struct matching Plug.Conn’s API conventions. This allows existing tests to migrate cleanly without bringing Cowboy, Ranch, or Plug into the dependency tree.

Thanks again for the input!

2 Likes

New passby v0.2.0, which ensures better compatibility with params (mainly path_params).