Skip to content

Commit

Permalink
Document Req.Test, closes #287
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtekmach committed Feb 19, 2024
1 parent 7f2d4b7 commit 24287c1
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ write new ones.

* Basic HTTP caching (via [`cache`] step.)

* Easily create test stubs (see [`Req.Test`].)

* Running against a plug (via [`put_plug`] step.)

* Pluggable adapters. By default, Req uses [Finch] (via [`run_finch`] step.)
Expand Down Expand Up @@ -238,6 +240,7 @@ limitations under the License.
[`Req.async_request/2`]: https://hexdocs.pm/req/Req.html#async_request/2
[`Req.Request`]: https://hexdocs.pm/req/Req.Request.html
[`Req.Steps`]: https://hexdocs.pm/req/Req.Steps.html
[`Req.Test`]: https://hexdocs.pm/req/Req.Test.html

[`auth`]: https://hexdocs.pm/req/Req.Steps.html#auth/1
[`cache`]: https://hexdocs.pm/req/Req.Steps.html#cache/1
Expand Down
108 changes: 108 additions & 0 deletions lib/req/test.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,114 @@
defmodule Req.Test do
@moduledoc """
Functions for creating test stubs.
> Stubs provide canned answers to calls made during the test, usually not responding at all to
> anything outside what's programmed in for the test.
>
> ["Mocks Aren't Stubs" by Martin Fowler](https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs)
Req has built-in support for stubs via `:plug`, `:adapter`, and (indirectly) `:base_url`
options. This module enhances these capabilities by providing:
* Stub any value with [`Req.Test.stub(name, value)`](`stub/2`) and access it with
[`Req.Test.stub(name)`](`stub/1`). These functions can be used in concurrent tests.
* Access plug stubs with `plug: {Req.Test, name}`.
* Easily create JSON responses for Plug stubs with [`Req.Test.json(conn, body)`](`json/2`).
## Example
Imagine we're building an app that displays weather for a given location using an HTTP weather
service:
defmodule MyApp.Weather do
def get_rating(location) do
case get_temperature(location) do
{:ok, %{status: 200, body: %{"celsius" => celsius"}}} ->
cond do
celsius < 18.0 -> {:ok, :too_cold}
celsius < 30.0 -> {:ok, :nice}
true -> {:ok, :too_hot}
end
_ ->
:error
end
end
def get_temperature(location) do
[
base_url: "https://weather-service"
]
|> Keyword.merge(Application.get_env(:myapp, :weather_req_options, []))
|> Req.request(options)
end
end
We configure it for production:
# config/runtime.exs
config :myapp, weather_req_options: [
auth: {:bearer, System.fetch_env!("MYAPP_WEATHER_API_KEY")}
]
And tests:
# config/runtime.exs
config :myapp, weather_req_options: [
plug: {Req.Test, MyApp.Weather}
]
And now we can easily stub out values **in concurrent tests**:
use ExUnit.Case, async: true
test "nice weather" do
Req.Test.stub(MyApp.Weather, fn conn ->
Req.Test.json(conn, %{"celsius" => 25.0})
end)
assert MyApp.Weather.get_rating("Krakow, Poland") == {:ok, :nice}
end
"""

@ownership Req.Ownership

@doc """
Sends JSON response.
## Examples
iex> plug = fn conn ->
...> Req.Test.json(conn, %{celsius: 25.0})
...> end
iex>
iex> resp = Req.get!(plug: plug)
iex> resp.headers["content-type"]
["application/json; charset=utf-8"]
iex> resp.body
%{"celsius" => 25.0}
"""
def json(%Plug.Conn{} = conn, data) do
send_resp(conn, conn.status || 200, "application/json", Jason.encode_to_iodata!(data))
end

defp send_resp(conn, default_status, default_content_type, body) do
conn
|> ensure_resp_content_type(default_content_type)
|> Plug.Conn.send_resp(conn.status || default_status, body)
end

defp ensure_resp_content_type(%Plug.Conn{resp_headers: resp_headers} = conn, content_type) do
if List.keyfind(resp_headers, "content-type", 0) do
conn
else
content_type = content_type <> "; charset=utf-8"
%Plug.Conn{conn | resp_headers: [{"content-type", content_type} | resp_headers]}
end
end

@doc """
Returns the stub created by `stub/2`.
"""
Expand Down
1 change: 1 addition & 0 deletions test/req/test_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule Req.TestTest do
use ExUnit.Case, async: true
doctest Req.Test

test "stub" do
assert_raise RuntimeError, ~r/cannot find stub/, fn ->
Expand Down

0 comments on commit 24287c1

Please sign in to comment.