Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Req.Test.expect/3 #325

Merged
merged 4 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 54 additions & 3 deletions lib/req/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,25 @@ defmodule Req.Test do
def stub(stub_name) do
case NimbleOwnership.fetch_owner(@ownership, callers(), stub_name) do
{:ok, owner} when is_pid(owner) ->
%{^stub_name => value} = NimbleOwnership.get_owned(@ownership, owner)
value
result =
NimbleOwnership.get_and_update(@ownership, owner, stub_name, fn
%{expectations: [value | rest]} = map ->
{{:ok, value}, put_in(map[:expectations], rest)}

%{stub: value} = map ->
{{:ok, value}, map}

%{expectations: []} = map ->
{{:error, :no_expectations_and_no_stub}, map}
end)

case result do
{:ok, {:ok, value}} ->
value

{:ok, {:error, :no_expectations_and_no_stub}} ->
raise "no stub or expectations for #{inspect(stub_name)}"
end

:error ->
raise "cannot find stub #{inspect(stub_name)} in process #{inspect(self())}"
Expand Down Expand Up @@ -312,12 +329,46 @@ defmodule Req.Test do
"""
@spec stub(stub(), term()) :: :ok | {:error, Exception.t()}
def stub(stub_name, value) do
case NimbleOwnership.get_and_update(@ownership, self(), stub_name, fn _ -> {:ok, value} end) do
result =
NimbleOwnership.get_and_update(@ownership, self(), stub_name, fn map_or_nil ->
{:ok, put_in(map_or_nil || %{}, [:stub], value)}
end)

case result do
{:ok, :ok} -> :ok
{:error, error} -> {:error, error}
end
end

@doc """
Creates an expectation with the given `name` and `value`, expected to be fetched at
most `n` times.

This function allows stubbing _any_ value and later accessing it with `stub/1`.
It is safe to use in concurrent tests. If you fetch the value under `stub_name`
more than `n` times, this function raises a `RuntimeError`.

## Examples

iex> Req.Test.expect(MyStub, 2, :foo)
iex> Req.Test.stub(MyStub)
:foo
iex> Req.Test.stub(MyStub)
:foo
iex> Req.Test.stub(MyStub)
** (RuntimeError) no stub or expectations for MyStub

"""
@doc since: "0.4.15"
@spec expect(stub(), pos_integer(), term()) :: term()
def expect(stub_name, n \\ 1, value) when is_integer(n) and n > 0 do
values = List.duplicate(value, n)

NimbleOwnership.get_and_update(@ownership, self(), stub_name, fn map_or_nil ->
{:ok, Map.update(map_or_nil || %{}, :expectations, values, &(values ++ &1))}
end)
end

@doc """
Allows `pid_to_allow` to access `stub_name` provided that `owner` is already allowed.
"""
Expand Down
21 changes: 21 additions & 0 deletions test/req/test_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,27 @@ defmodule Req.TestTest do
assert Req.Test.stub(:foo) == 2
end

describe "expect/3" do
test "works in the normal expectation-based way" do
Req.Test.expect(:foo, 2, 1)
assert Req.Test.stub(:foo) == 1
assert Req.Test.stub(:foo) == 1

assert_raise RuntimeError, "no stub or expectations for :foo", fn ->
Req.Test.stub(:foo)
end
end

test "works with the default expected count of 1" do
Req.Test.expect(:foo_default, 1)
assert Req.Test.stub(:foo_default) == 1

assert_raise RuntimeError, "no stub or expectations for :foo_default", fn ->
assert Req.Test.stub(:foo_default)
end
end
end

describe "plug" do
test "function" do
Req.Test.stub(:foo, fn conn ->
Expand Down
Loading