We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
`Req.Test` stubs keep HTTP out of your tests
almirsarajcic
Req ships with a built-in test stub mechanism. No Bypass, no Mox, no additional dependencies — just three small changes and your HTTP calls never hit the network in tests.
# 1. lib/my_app/weather_client.ex — read options from config
defmodule MyApp.WeatherClient do
def get_forecast(city) do
req_options = Application.get_env(:my_app, :weather_req_options, [])
Req.new(base_url: "https://api.weather.com")
|> Req.merge(req_options)
|> Req.get!(url: "/forecast", params: [city: city])
end
end
# 2. config/test.exs — point the plug at Req.Test
config :my_app, :weather_req_options,
plug: {Req.Test, MyApp.WeatherClient},
retry: false
# 3. test — stub and assert
test "get_forecast/1 returns the forecast for a city" do
Req.Test.stub(MyApp.WeatherClient, fn conn ->
Req.Test.json(conn, %{"forecast" => "sunny", "city" => "London"})
end)
response = MyApp.WeatherClient.get_forecast("London")
assert response.body["forecast"] == "sunny"
end
The stub receives a Plug.Conn — the same conn you know from Phoenix. Req.Test.json/2 is a convenience that sets the content-type header and JSON-encodes the response body for you.
The key insight is that plug: {Req.Test, MyApp.WeatherClient} acts as a transport-level interceptor. Instead of opening a TCP connection, Req calls your plug function directly. The test process owns the stub, so concurrent tests with async: true are safe without any extra setup.
You can inspect the outgoing request inside the stub too — useful when you want to assert on what your code is actually sending:
Req.Test.stub(MyApp.WeatherClient, fn conn ->
assert conn.params["city"] == "London"
Req.Test.json(conn, %{"forecast" => "sunny"})
end)
And Req.Test.transport_error/2 lets you simulate network failures to test your error handling:
Req.Test.stub(MyApp.WeatherClient, fn conn ->
Req.Test.transport_error(conn, :timeout)
end)
If your Req call happens inside a spawned Task, the stub won’t be visible to the child process by default. Pass ownership explicitly with Req.Test.allow/3 — or see Task.Supervisor sandbox allowance patterns for the full setup.
copied to clipboard