Page MenuHomePhorge

No OneTemporary

Size
19 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla/mock.ex b/lib/tesla/mock.ex
index 9d8c602..0d846f1 100644
--- a/lib/tesla/mock.ex
+++ b/lib/tesla/mock.ex
@@ -1,174 +1,180 @@
defmodule Tesla.Mock do
@moduledoc """
Mock adapter for better testing.
### Setup
```
# config/test.exs
config :tesla, adapter: Tesla.Mock
# in case MyClient defines specific adapter with `adapter :specific`
config :tesla, MyClient, adapter: Tesla.Mock
```
### Example test
```
defmodule MyAppTest do
use ExUnit.Case
setup do
Tesla.Mock.mock fn
%{method: :get} ->
%Tesla.Env{status: 200, body: "hello"}
end
:ok
end
test "list things" do
assert %Tesla.Env{} = env = MyApp.get("...")
assert env.status == 200
assert env.body == "hello"
end
end
```
### Setting up mocks
```
# Match on method & url and return whole Tesla.Env
Tesla.Mock.mock fn
%{method: :get, url: "http://example.com/list"} ->
%Tesla.Env{status: 200, body: "hello"}
end
# You can use any logic required
Tesla.Mock.mock fn env ->
case env.url do
"http://example.com/list" ->
%Tesla.Env{status: 200, body: "ok!"}
_ ->
%Tesla.Env{status: 404, body: "NotFound"}
end
# mock will also accept short version of response
# in the form of {status, headers, body}
Tesla.Mock.mock fn
%{method: :post} -> {201, %{}, %{id: 42}}
end
```
### Global mocks
By default, mocks are bound to the current process,
i.e. the process running a single test case.
This design allows proper isolation between test cases
and make testing in parallel (`async: true`) possible.
While this style is recommended, there is one drawback:
if Tesla client is called from different process
it will not use the setup mock.
To solve this issue it is possible to setup a global mock
using `mock_global/1` function.
```
defmodule MyTest do
use ExUnit.Case, async: false # must be false!
setup_all do
Tesla.Mock.mock_global fn
env -> # ...
end
:ok
end
# ...
end
```
**WARNING**: Using global mocks may affect tests with local mock
(because of fallback to global mock in case local one is not found)
"""
defmodule Error do
defexception env: nil, ex: nil, stacktrace: []
def message(%__MODULE__{ex: nil}) do
"""
There is no mock set for process #{inspect(self())}.
Use Tesla.Mock.mock/1 to mock HTTP requests.
See https://github.com/teamon/tesla#testing
"""
end
def message(%__MODULE__{env: env, ex: %FunctionClauseError{} = ex, stacktrace: stacktrace}) do
"""
Request not mocked
The following request was not mocked:
#{inspect(env, pretty: true)}
#{Exception.format(:error, ex, stacktrace)}
"""
end
end
## PUBLIC API
@doc """
Setup mocks for current test.
This mock will only be available to the current process.
"""
@spec mock((Tesla.Env.t() -> Tesla.Env.t() | {integer, map, any})) :: no_return
def mock(fun) when is_function(fun), do: pdict_set(fun)
@doc """
Setup global mocks.
**WARNING**: This mock will be available to ALL processes.
It might cause conflicts when running tests in parallel!
"""
@spec mock_global((Tesla.Env.t() -> Tesla.Env.t() | {integer, map, any})) :: no_return
def mock_global(fun) when is_function(fun), do: agent_set(fun)
## ADAPTER IMPLEMENTATION
def call(env, _opts) do
case pdict_get() || agent_get() do
nil ->
raise Tesla.Mock.Error, env: env
fun ->
case rescue_call(fun, env) do
{status, headers, body} ->
- %{env | status: status, headers: headers, body: body}
+ {:ok, %{env | status: status, headers: headers, body: body}}
%Tesla.Env{} = env ->
- env
+ {:ok, env}
+
+ {:ok, %Tesla.Env{} = env} ->
+ {:ok, env}
+
+ {:error, reason} ->
+ {:error, reason}
end
end
end
defp pdict_set(fun), do: Process.put(__MODULE__, fun)
defp pdict_get, do: Process.get(__MODULE__)
defp agent_set(fun), do: {:ok, _pid} = Agent.start_link(fn -> fun end, name: __MODULE__)
defp agent_get do
case Process.whereis(__MODULE__) do
nil -> nil
pid -> Agent.get(pid, fn f -> f end)
end
end
defp rescue_call(fun, env) do
fun.(env)
rescue
ex in FunctionClauseError ->
raise Tesla.Mock.Error, env: env, ex: ex, stacktrace: System.stacktrace()
end
end
diff --git a/test/tesla/global_mock_test.exs b/test/tesla/global_mock_test.exs
index d0b683f..6393b08 100644
--- a/test/tesla/global_mock_test.exs
+++ b/test/tesla/global_mock_test.exs
@@ -1,19 +1,19 @@
defmodule Tesla.GlobalMockTest do
use ExUnit.Case, async: false
setup_all do
Tesla.Mock.mock_global(fn
%{method: :get, url: "http://example.com/list"} -> %Tesla.Env{status: 200, body: "hello"}
%{method: :post, url: "http://example.com/create"} -> {201, %{}, %{id: 42}}
end)
:ok
end
test "mock request from spawned process" do
pid = self()
spawn(fn -> send(pid, MockClient.list()) end)
- assert_receive %Tesla.Env{status: 200, body: "hello"}
+ assert_receive {:ok, %Tesla.Env{status: 200, body: "hello"}}
end
end
diff --git a/test/tesla/mock/global_a_test.exs b/test/tesla/mock/global_a_test.exs
index beb3ad0..85a67a6 100644
--- a/test/tesla/mock/global_a_test.exs
+++ b/test/tesla/mock/global_a_test.exs
@@ -1,14 +1,14 @@
defmodule Tesla.Mock.GlobalATest do
use ExUnit.Case, async: false
setup_all do
Tesla.Mock.mock_global(fn _env -> %Tesla.Env{status: 200, body: "AAA"} end)
:ok
end
test "mock get request" do
- assert %Tesla.Env{} = env = MockClient.get("/")
+ assert {:ok, %Tesla.Env{} = env} = MockClient.get("/")
assert env.body == "AAA"
end
end
diff --git a/test/tesla/mock/global_b_test.exs b/test/tesla/mock/global_b_test.exs
index bda8293..bdb5a4e 100644
--- a/test/tesla/mock/global_b_test.exs
+++ b/test/tesla/mock/global_b_test.exs
@@ -1,14 +1,14 @@
defmodule Tesla.Mock.GlobalBTest do
use ExUnit.Case, async: false
setup_all do
Tesla.Mock.mock_global(fn _env -> %Tesla.Env{status: 200, body: "BBB"} end)
:ok
end
test "mock get request" do
- assert %Tesla.Env{} = env = MockClient.get("/")
+ assert {:ok, %Tesla.Env{} = env} = MockClient.get("/")
assert env.body == "BBB"
end
end
diff --git a/test/tesla/mock/local_a_test.exs b/test/tesla/mock/local_a_test.exs
index 396f5b2..6bfaf3d 100644
--- a/test/tesla/mock/local_a_test.exs
+++ b/test/tesla/mock/local_a_test.exs
@@ -1,14 +1,14 @@
defmodule Tesla.Mock.LocalATest do
use ExUnit.Case, async: true
setup do
Tesla.Mock.mock(fn _env -> %Tesla.Env{status: 200, body: "AAA"} end)
:ok
end
test "mock get request" do
- assert %Tesla.Env{} = env = MockClient.get("/")
+ assert {:ok, %Tesla.Env{} = env} = MockClient.get("/")
assert env.body == "AAA"
end
end
diff --git a/test/tesla/mock/local_b_test.exs b/test/tesla/mock/local_b_test.exs
index f6fea8e..a048a9d 100644
--- a/test/tesla/mock/local_b_test.exs
+++ b/test/tesla/mock/local_b_test.exs
@@ -1,14 +1,14 @@
defmodule Tesla.Mock.LocalBTest do
use ExUnit.Case, async: true
setup do
Tesla.Mock.mock(fn _env -> %Tesla.Env{status: 200, body: "BBB"} end)
:ok
end
test "mock get request" do
- assert %Tesla.Env{} = env = MockClient.get("/")
+ assert {:ok, %Tesla.Env{} = env} = MockClient.get("/")
assert env.body == "BBB"
end
end
diff --git a/test/tesla/mock_test.exs b/test/tesla/mock_test.exs
index 7b08869..b827270 100644
--- a/test/tesla/mock_test.exs
+++ b/test/tesla/mock_test.exs
@@ -1,45 +1,45 @@
defmodule Tesla.MockTest do
use ExUnit.Case
defp setup_mock(_) do
Tesla.Mock.mock(fn
%{method: :get, url: "http://example.com/list"} ->
%Tesla.Env{status: 200, body: "hello"}
%{method: :post, url: "http://example.com/create", body: ~s({"some":"data"})} ->
{201, [{"content-type", "application/json"}], ~s"{\"id\":42}"}
end)
:ok
end
describe "with mock" do
setup :setup_mock
test "mock get request" do
- assert %Tesla.Env{} = env = MockClient.list()
+ assert {:ok, %Tesla.Env{} = env} = MockClient.list()
assert env.status == 200
assert env.body == "hello"
end
test "raise on unmocked request" do
assert_raise Tesla.Mock.Error, fn ->
MockClient.search()
end
end
test "mock post request" do
- assert %Tesla.Env{} = env = MockClient.create(%{"some" => "data"})
+ assert {:ok, %Tesla.Env{} = env} = MockClient.create(%{"some" => "data"})
assert env.status == 201
assert env.body == %{"id" => 42}
end
end
describe "without mock" do
test "raise on unmocked request" do
assert_raise Tesla.Mock.Error, fn ->
MockClient.search()
end
end
end
end
diff --git a/test/tesla_test.exs b/test/tesla_test.exs
index 3d6e40f..d59d55c 100644
--- a/test/tesla_test.exs
+++ b/test/tesla_test.exs
@@ -1,298 +1,308 @@
defmodule TeslaTest do
use ExUnit.Case
require Tesla
@url "http://localhost:#{Application.get_env(:httparrot, :http_port)}"
describe "Adapters" do
defmodule ModuleAdapter do
def call(env, opts \\ []) do
- Map.put(env, :url, env.url <> "/module/" <> opts[:with])
+ {:ok, Map.put(env, :url, env.url <> "/module/" <> opts[:with])}
end
end
defmodule EmptyClient do
use Tesla
end
defmodule ModuleAdapterClient do
use Tesla
adapter ModuleAdapter, with: "someopt"
end
defmodule LocalAdapterClient do
use Tesla
adapter :local_adapter
def local_adapter(env) do
- Map.put(env, :url, env.url <> "/local")
+ {:ok, Map.put(env, :url, env.url <> "/local")}
end
end
defmodule FunAdapterClient do
use Tesla
adapter fn env ->
- Map.put(env, :url, env.url <> "/anon")
+ {:ok, Map.put(env, :url, env.url <> "/anon")}
end
end
setup do
# clean config
Application.delete_env(:tesla, EmptyClient)
Application.delete_env(:tesla, ModuleAdapterClient)
:ok
end
test "defauilt adapter" do
assert Tesla.effective_adapter(EmptyClient) == {Tesla.Adapter.Httpc, :call, [[]]}
end
+ test "use adapter override from config" do
+ Application.put_env(:tesla, EmptyClient, adapter: Tesla.Mock)
+ assert Tesla.effective_adapter(EmptyClient) == {Tesla.Mock, :call, [[]]}
+ end
+
+ test "prefer config over module setting" do
+ Application.put_env(:tesla, ModuleAdapterClient, adapter: Tesla.Mock)
+ assert Tesla.effective_adapter(ModuleAdapterClient) == {Tesla.Mock, :call, [[]]}
+ end
+
test "execute module adapter" do
- response = ModuleAdapterClient.request(url: "test")
+ assert {:ok, response} = ModuleAdapterClient.request(url: "test")
assert response.url == "test/module/someopt"
end
test "execute local function adapter" do
- response = LocalAdapterClient.request(url: "test")
+ assert {:ok, response} = LocalAdapterClient.request(url: "test")
assert response.url == "test/local"
end
test "execute anonymous function adapter" do
- response = FunAdapterClient.request(url: "test")
+ assert {:ok, response} = FunAdapterClient.request(url: "test")
assert response.url == "test/anon"
end
-
- test "use adapter override from config" do
- Application.put_env(:tesla, EmptyClient, adapter: Tesla.Mock)
- assert Tesla.effective_adapter(EmptyClient) == {Tesla.Mock, :call, [[]]}
- end
-
- test "prefer config over module setting" do
- Application.put_env(:tesla, ModuleAdapterClient, adapter: Tesla.Mock)
- assert Tesla.effective_adapter(ModuleAdapterClient) == {Tesla.Mock, :call, [[]]}
- end
end
describe "Middleware" do
defmodule AppendOne do
@behaviour Tesla.Middleware
def call(env, next, _opts) do
env
|> Map.put(:url, "#{env.url}/1")
|> Tesla.run(next)
end
end
defmodule AppendWith do
@behaviour Tesla.Middleware
def call(env, next, opts) do
env
|> Map.update!(:url, fn url -> url <> "/MB" <> opts[:with] end)
|> Tesla.run(next)
- |> Map.update!(:url, fn url -> url <> "/MA" <> opts[:with] end)
+ |> case do
+ {:ok, env} ->
+ {:ok, Map.update!(env, :url, fn url -> url <> "/MA" <> opts[:with] end)}
+ error ->
+ error
+ end
end
end
defmodule AppendClient do
use Tesla
plug AppendOne
plug AppendWith, with: "1"
plug AppendWith, with: "2"
plug :local_middleware
- adapter fn env -> env end
+ adapter fn env -> {:ok, env} end
def local_middleware(env, next) do
env
|> Map.update!(:url, fn url -> url <> "/LB" end)
|> Tesla.run(next)
- |> Map.update!(:url, fn url -> url <> "/LA" end)
+ |> case do
+ {:ok, env} ->
+ {:ok, Map.update!(env, :url, fn url -> url <> "/LA" end)}
+ error ->
+ error
+ end
end
end
test "execute middleware top down" do
- response = AppendClient.get("one")
+ assert {:ok, response} = AppendClient.get("one")
assert response.url == "one/1/MB1/MB2/LB/LA/MA2/MA1"
end
end
describe "Dynamic client" do
defmodule DynamicClient do
use Tesla
adapter fn env ->
if String.ends_with?(env.url, "/cached") do
- %{env | body: "cached", status: 304}
+ {:ok, %{env | body: "cached", status: 304}}
else
Tesla.run_default_adapter(env)
end
end
def help(client \\ %Tesla.Client{}) do
get(client, "/help")
end
end
test "override adapter - Tesla.build_client" do
client =
Tesla.build_client([], [
fn env, _next ->
- %{env | body: "new"}
+ {:ok, %{env | body: "new"}}
end
])
- assert %{body: "new"} = DynamicClient.help(client)
+ assert {:ok, %{body: "new"}} = DynamicClient.help(client)
end
test "override adapter - Tesla.build_adapter" do
client =
Tesla.build_adapter(fn env ->
- %{env | body: "new"}
+ {:ok, %{env | body: "new"}}
end)
- assert %{body: "new"} = DynamicClient.help(client)
+ assert {:ok, %{body: "new"}} = DynamicClient.help(client)
end
test "statically override adapter" do
- assert %{status: 200} = DynamicClient.get(@url <> "/ip")
- assert %{status: 304} = DynamicClient.get(@url <> "/cached")
+ assert {:ok, %{status: 200}} = DynamicClient.get(@url <> "/ip")
+ assert {:ok, %{status: 304}} = DynamicClient.get(@url <> "/cached")
end
end
describe "request API" do
defmodule SimpleClient do
use Tesla
adapter fn env ->
- env
+ {:ok, env}
end
end
test "basic request" do
- response = SimpleClient.request(url: "/", method: :post, query: [page: 1], body: "data")
+ assert {:ok, response} = SimpleClient.request(url: "/", method: :post, query: [page: 1], body: "data")
assert response.method == :post
assert response.url == "/"
assert response.query == [page: 1]
assert response.body == "data"
end
test "shortcut function" do
- response = SimpleClient.get("/get")
+ assert {:ok, response} = SimpleClient.get("/get")
assert response.method == :get
assert response.url == "/get"
end
test "shortcut function with body" do
- response = SimpleClient.post("/post", "some-data")
+ assert {:ok, response} = SimpleClient.post("/post", "some-data")
assert response.method == :post
assert response.url == "/post"
assert response.body == "some-data"
end
test "request with client" do
client = fn env, next ->
env
|> Map.put(:url, "/prefix" <> env.url)
|> Tesla.run(next)
end
- response = SimpleClient.get("/")
+ assert {:ok, response} = SimpleClient.get("/")
assert response.url == "/"
assert response.__client__ == %Tesla.Client{}
- response = client |> SimpleClient.get("/")
+ assert {:ok, response} = client |> SimpleClient.get("/")
assert response.url == "/prefix/"
assert response.__client__ == %Tesla.Client{fun: client}
end
test "better errors when given nil opts" do
assert_raise FunctionClauseError, fn ->
Tesla.get("/", nil)
end
end
end
alias Tesla.Env
import Tesla
describe "get_header/2" do
test "non existing header" do
env = %Env{headers: [{"server", "Cowboy"}]}
assert get_header(env, "some-key") == nil
end
test "existing header" do
env = %Env{headers: [{"server", "Cowboy"}]}
assert get_header(env, "server") == "Cowboy"
end
test "first of multiple headers with the same name" do
env = %Env{headers: [{"cookie", "chocolate"}, {"cookie", "biscuits"}]}
assert get_header(env, "cookie") == "chocolate"
end
end
describe "get_headers/2" do
test "none matching" do
env = %Env{headers: [{"server", "Cowboy"}]}
assert get_headers(env, "cookie") == []
end
test "multiple matches matching" do
env = %Env{headers: [{"cookie", "chocolate"}, {"cookie", "biscuits"}]}
assert get_headers(env, "cookie") == ["chocolate", "biscuits"]
end
end
describe "put_header/3" do
test "add new header" do
env = %Env{}
env = put_header(env, "server", "Cowboy")
assert get_header(env, "server") == "Cowboy"
end
test "override existing header" do
env = %Env{headers: [{"server", "Cowboy"}]}
env = put_header(env, "server", "nginx")
assert get_header(env, "server") == "nginx"
end
end
describe "put_headers/2" do
test "add headers to env existing header" do
env = %Env{}
assert get_header(env, "server") == nil
env = Tesla.put_headers(env, [{"server", "Cowboy"}, {"content-length", "100"}])
assert get_header(env, "server") == "Cowboy"
assert get_header(env, "content-length") == "100"
env = Tesla.put_headers(env, [{"server", "nginx"}, {"content-type", "text/plain"}])
assert get_header(env, "server") == "Cowboy"
assert get_header(env, "content-length") == "100"
assert get_header(env, "content-type") == "text/plain"
end
test "add multiple headers with the same name" do
env = %Env{}
env = Tesla.put_headers(env, [{"cookie", "chocolate"}, {"cookie", "biscuits"}])
assert get_headers(env, "cookie") == ["chocolate", "biscuits"]
end
end
describe "delete_header/2" do
test "delete all headers with given name" do
env = %Env{headers: [{"cookie", "chocolate"}, {"server", "Cowboy"}, {"cookie", "biscuits"}]}
env = delete_header(env, "cookie")
assert get_header(env, "cookie") == nil
assert get_header(env, "server") == "Cowboy"
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Tue, Nov 26, 2:34 AM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40178
Default Alt Text
(19 KB)

Event Timeline