269 words, 2 min read

When a module relies on external HTTP requests, your tests should not. The Req.Test module provides a clean way to stub responses and keep your tests fast and predictable. This post shows how to configure your application for testing, integrate Req.Test into your HTTP client, and write focused tests with controlled behaviour.

Test-specific configuration

To activate Req.Test only in the test environment, override your Req options in config/test.exs:

# config/test.exs
config :my_app,
http_client_options: [
plug: {Req.Test, MyApp.HTTPClient}
]

This ensures that all requests going through your client module will be intercepted by Req.Test during tests.

Building a Req client that merges configuration

Your client module stays environment-agnostic. It defines its base options and merges in whatever is configured for tests:

defmodule MyApp.HTTPClient do
@default_timeout 5_000
@max_retries 3
def new(url, headers \\ []) do
[
url: url,
redirect: true,
compressed: true,
headers: headers
]
|> Keyword.merge(Application.get_env(:my_app, :http_client_options, []))
|> Req.new()
end
end

In non-test environments, the merge adds nothing and the client performs real requests. In tests, the plug activates the Req.Test pipeline.

Writing tests with stubbed responses

Req.Test.stub/2 lets you define responses for each request made via the client:

test "handles 304 responses" do
Req.Test.stub(MyApp.HTTPClient, fn conn ->
conn
|> resp(304, "body")
end)
response = MyApp.HTTPClient.new("https://example.com/")
|> Req.request()
assert response.status == 304
end

Each test run receives the same deterministic response, keeping your test suite stable and independent of the network.

Conclusion

Req.Test is a straightforward and powerful tool for isolating your tests from external HTTP dependencies. By pushing configuration into your test environment and keeping your client code clean, you gain reproducible tests without sacrificing clarity or maintainability.