Packages
tesla
1.18.1
1.20.0
1.18.3
1.18.2
1.18.1
1.18.0
1.17.0
1.16.0
1.15.3
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.3
1.12.2
1.12.1
1.12.0
1.11.2
1.11.1
1.11.0
1.10.3
1.10.2
1.10.1
1.10.0
1.9.0
1.8.1
retired
1.8.0
1.7.0
1.6.1
1.6.0
1.5.1
1.5.0
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.0
1.0.0
1.0.0-beta.1
0.10.0
0.9.0
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.2.2
0.2.1
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
HTTP client library, with support for middleware and multiple adapters.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/tesla/test.ex
defmodule Tesla.Test do
@moduledoc """
Provides utilities for testing Tesla-based HTTP clients.
"""
import ExUnit.Assertions
@doc """
Asserts that two `t:Tesla.Env.t/0` structs match.
## Parameters
- `given_env` - The actual `t:Tesla.Env.t/0` struct received from the request.
- `expected_env` - The expected `t:Tesla.Env.t/0` struct to compare against.
- `opts` - Additional options for fine-tuning the assertion (optional).
- `:exclude_headers` - A list of header keys to exclude from the assertion.
For the `body`, the function attempts to parse JSON and URL-encoded content
when appropriate.
This function is designed to be used in conjunction with
`Tesla.Test.assert_received_tesla_call/1` for comprehensive request
testing.
## Examples
defmodule MyTest do
use ExUnit.Case, async: true
require Tesla.Test
test "returns a 200 status" do
given_env = %Tesla.Env{
method: :post,
url: "https://acme.com/users",
}
Tesla.Test.assert_tesla_env(given_env, %Tesla.Env{
method: :post,
url: "https://acme.com/users",
})
end
end
"""
def assert_tesla_env(%Tesla.Env{} = given_env, %Tesla.Env{} = expected_env, opts \\ []) do
exclude_headers = Keyword.get(opts, :exclude_headers, [])
given_headers =
for {key, value} <- given_env.headers, key not in exclude_headers, do: {key, value}
assert given_env.method == expected_env.method
assert given_env.url == expected_env.url
assert given_headers == expected_env.headers
assert given_env.query == expected_env.query
assert read_body!(given_env) == expected_env.body
end
@doc """
Puts an HTML response.
iex> Tesla.Test.html(%Tesla.Env{}, "<html><body>Hello, world!</body></html>")
%Tesla.Env{
body: "<html><body>Hello, world!</body></html>",
headers: [{"content-type", "text/html; charset=utf-8"}],
...
}
"""
@spec html(%Tesla.Env{}, binary) :: %Tesla.Env{}
def html(%Tesla.Env{} = env, body) when is_binary(body) do
env
|> put_body(body)
|> put_headers([{"content-type", "text/html; charset=utf-8"}])
end
@doc """
Puts a JSON response.
iex> Tesla.Test.json(%Tesla.Env{}, %{"some" => "data"})
%Tesla.Env{
body: ~s({"some":"data"}),
headers: [{"content-type", "application/json; charset=utf-8"}],
...
}
If the body is binary, it will be returned as is and it will not try to encode
it to JSON.
"""
@spec json(%Tesla.Env{}, term) :: %Tesla.Env{}
def json(%Tesla.Env{} = env, body) do
body = encode!(body, "application/json")
env
|> put_body(body)
|> put_headers([{"content-type", "application/json; charset=utf-8"}])
end
@doc """
Puts a text response.
iex> Tesla.Test.text(%Tesla.Env{}, "Hello, world!")
%Tesla.Env{
body: "Hello, world!",
headers: [{"content-type", "text/plain; charset=utf-8"}],
...
}
"""
@spec text(%Tesla.Env{}, binary) :: %Tesla.Env{}
def text(%Tesla.Env{} = env, body) when is_binary(body) do
env
|> put_body(body)
|> put_headers([{"content-type", "text/plain; charset=utf-8"}])
end
@doc """
Asserts that the current process's mailbox does not contain any `Tesla.Test`
messages.
This function is designed to be used in conjunction with
`Tesla.Test.assert_received_tesla_call/1` for comprehensive request
testing.
"""
defmacro assert_tesla_empty_mailbox do
quote do
refute_received {Tesla.Test, _}
end
end
@doc """
Asserts that the current process's mailbox contains a `TeslaMox` message.
It uses `assert_received/1` under the hood.
## Parameters
- `expected_env` - The expected `t:Tesla.Env.t/0` passed to the adapter.
- `expected_opts` - The expected `t:Tesla.Adapter.options/0` passed to the
adapter.
- `opts` - Extra configuration options.
- `:adapter` - Optional. The adapter to expect the call on. Falls back to
the `:tesla` application configuration.
## Examples
Asserting that the adapter received a `t:Tesla.Env.t/0` struct with a `200`
status:
defmodule MyTest do
use ExUnit.Case, async: true
require Tesla.Test
test "returns a 200 status" do
# given - preconditions
Tesla.Test.expect_tesla_call(
times: 2,
returns: %Tesla.Env{status: 200, body: "OK"}
)
# when - run unit of work
# ... do some work ...
Tesla.post!("https://acme.com/users")
# ...
# then - assertions
Tesla.Test.assert_received_tesla_call(expected_env, expected_opts)
Tesla.Test.assert_tesla_env(expected_env, %Tesla.Env{
url: "https://acme.com/users",
status: 200,
body: "OK"
})
assert expected_opts == []
Tesla.Test.assert_tesla_empty_mailbox()
end
end
"""
defmacro assert_received_tesla_call(expected_env, expected_opts \\ [], opts \\ []) do
adapter = fetch_adapter!(opts)
quote do
assert_received {Tesla.Test,
{unquote(adapter), :call, [unquote(expected_env), unquote(expected_opts)]}}
end
end
if Code.ensure_loaded?(Mox) do
@doc """
Expects a call on the given adapter using `Mox.expect/4`. Only available when
`Mox` is loaded.
## Options
- `:times` - Required. The number of times to expect the call.
- `:returns` - Required. The value to return from the adapter.
- `:send_to` - Optional. The process to send the message to. Defaults to
the current process.
- `:adapter` - Optional. The adapter to expect the call on. Falls back to
the `:tesla` application configuration.
## Examples
Returning a `t:Tesla.Env.t/0` struct with a `200` status:
Tesla.Test.expect_tesla_call(
times: 2,
returns: %Tesla.Env{status: 200}
)
Changing the `Mox` mocked adapter:
Tesla.Test.expect_tesla_call(
times: 2,
returns: %Tesla.Env{status: 200},
adapter: MyApp.MockAdapter
)
"""
def expect_tesla_call(opts) do
n_time = Keyword.fetch!(opts, :times)
adapter = fetch_adapter!(opts)
send_to = Keyword.get(opts, :send_to, self())
Mox.expect(adapter, :call, n_time, fn given_env, given_opts ->
if send_to != nil do
message = {adapter, :call, [given_env, given_opts]}
send(send_to, {Tesla.Test, message})
end
case Keyword.fetch!(opts, :returns) do
fun when is_function(fun) ->
fun.(given_env, given_opts)
%Tesla.Env{} = value ->
{:ok, Map.merge(given_env, Map.take(value, [:body, :headers, :status]))}
{:error, error} ->
{:error, error}
end
end)
end
end
defp encode!(body, "application/json") when is_binary(body), do: body
defp encode!(body, "application/json"), do: Jason.encode!(body)
defp encode!(body, _), do: body
defp read_body!(%Tesla.Env{} = env) do
case Tesla.get_headers(env, "content-type") do
["application/json" | _] -> Jason.decode!(env.body, keys: :atoms)
["application/x-www-form-urlencoded" | _] -> URI.decode_query(env.body)
_ -> env.body
end
end
defp fetch_adapter!(opts) do
adapter =
Keyword.get_lazy(opts, :adapter, fn ->
Application.get_env(:tesla, :adapter)
end)
case adapter do
nil ->
raise ArgumentError, """
expected :adapter to be defined
Set in the opts[:adapter]. Or set in the `config/test.exs`
configuration:
config :tesla, :adapter, MyApp.MockAdapter
"""
adapter ->
adapter
end
end
defp put_body(%Tesla.Env{} = env, body) do
%{env | body: body}
end
defp put_headers(%Tesla.Env{headers: nil} = env, headers) when is_list(headers) do
%{env | headers: headers}
end
defp put_headers(%Tesla.Env{} = env, headers) when is_list(headers) do
%{env | headers: env.headers ++ headers}
end
end