Skip to content

Commit fee2662

Browse files
authored
feat!: change expect/3,4 to not call original once fulfilled (#98)
1 parent 6438f76 commit fee2662

File tree

7 files changed

+132
-15
lines changed

7 files changed

+132
-15
lines changed

CHANGELOG.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# 2.0.0 (2025-07-13)
2+
3+
## Breaking changes
4+
5+
The code below would call the original `Calculator.add/2` when all expectations were fulfilled.
6+
7+
```elixir
8+
Calculator
9+
|> expect(:add, fn _, _ -> :expected1 end)
10+
|> expect(:add, fn _, _ -> :expected2 end)
11+
12+
assert Calculator.add(1, 1) == :expected1
13+
assert Calculator.add(1, 1) == :expected2
14+
assert Calculator.add(1, 1) == 2
15+
```
16+
17+
Now with Mimic 2 this will raise:
18+
19+
```elixir
20+
Calculator
21+
|> expect(:add, fn _, _ -> :expected1 end)
22+
|> expect(:add, fn _, _ -> :expected2 end)
23+
24+
assert Calculator.add(1, 1) == :expected1
25+
assert Calculator.add(1, 1) == :expected2
26+
Calculator.add(1, 1)
27+
# Will raise error because more than 2 calls to Calculator.add were made and there is no stub
28+
# ** (Mimic.UnexpectedCallError) Calculator.add/2 called in process #PID<.*> but expectations are already fulfilled
29+
```
30+
31+
If there is a stub the stub will be called instead. This behaviour is the same as before.
32+
33+
```elixir
34+
Calculator
35+
|> expect(:add, fn _, _ -> :expected1 end)
36+
|> expect(:add, fn _, _ -> :expected2 end)
37+
|> stub(:add, fn _, _ -> :stub end)
38+
39+
assert Calculator.add(1, 1) == :expected1
40+
assert Calculator.add(1, 1) == :expected2
41+
assert Calculator.add(1, 1) == :stub
42+
```
43+
44+
Which means that if someone wants to keep the original behaviour on Mimic 1.* just do the following:
45+
46+
```elixir
47+
Calculator
48+
|> expect(:add, fn _, _ -> :expected1 end)
49+
|> expect(:add, fn _, _ -> :expected2 end)
50+
|> stub(:add, fn x, y -> call_original(Calculator, :add, [x, y]) end)
51+
52+
assert Calculator.add(1, 1) == :expected1
53+
assert Calculator.add(1, 1) == :expected2
54+
assert Calculator.add(1, 1) == 2
55+
```
56+
57+
This way once all expectations are fulfilled the original function is called again.

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Just add `:mimic` to your list of dependencies in `mix.exs`:
1717
```elixir
1818
def deps do
1919
[
20-
{:mimic, "~> 1.12", only: :test}
20+
{:mimic, "~> 2.0", only: :test}
2121
]
2222
end
2323
```
@@ -101,6 +101,10 @@ Calculator
101101

102102
assert Calculator.add(1, 3) == {:add, 1, 3}
103103
assert Calculator.add(4, 5) == {:add, 4, 5}
104+
Calculator.add(1, 4)
105+
106+
# Will raise error because more than 2 calls to Calculator.add were made and there is no stub
107+
# ** (Mimic.UnexpectedCallError) Calculator.add/2 called in process #PID<.*> but expectations are already fulfilled
104108
```
105109

106110
With `use Mimic`, verification `expect/4` function call of is done automatically on test case end. `verify!/1` can be used in case custom verification timing required:

lib/mimic.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ defmodule Mimic do
196196
function must be called within the lifetime of the calling `pid` (i.e. the
197197
test example).
198198
199+
Once the expectation is fulfilled any calls to this MFA will raise an error
200+
unless there is a stub through `stub/3`
201+
199202
## Arguments:
200203
201204
* `module` - the name of the module in which we're adding the stub.

lib/mimic/server.ex

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ defmodule Mimic.Server do
139139
:original ->
140140
apply_original(module, fn_name, args)
141141

142+
{:unexpected, :fulfilled} ->
143+
mfa = Exception.format_mfa(module, fn_name, arity)
144+
145+
raise Mimic.UnexpectedCallError,
146+
"#{mfa} called in process #{inspect(self())} but expectations are already fulfilled"
147+
142148
{:unexpected, num_calls, num_applied_calls} ->
143149
mfa = Exception.format_mfa(module, fn_name, arity)
144150

@@ -283,16 +289,22 @@ defmodule Mimic.Server do
283289
{:reply, {:unexpected, num_calls, num_applied_calls}, state}
284290
end
285291

286-
_ ->
287-
case find_stub(state.stubs, module, fn_name, arity, caller) do
288-
:unexpected ->
289-
{:reply, :original, state}
290-
291-
{:ok, func} ->
292+
expectations ->
293+
case {find_stub(state.stubs, module, fn_name, arity, caller), expectations} do
294+
{{:ok, func}, _} ->
292295
# Track call history for stubs too
293296
state = put_call_history(state, caller, module, fn_name, arity, args)
294297

295298
{:reply, {:ok, func}, state}
299+
300+
{:unexpected, []} ->
301+
# expectations for this mfa existed but they have all been fulfilled
302+
{:reply, {:unexpected, :fulfilled}, state}
303+
304+
{:unexpected, nil} ->
305+
# no expectations were ever defined for this mfa
306+
# This case happens when another mfa was set-up
307+
{:reply, :original, state}
296308
end
297309
end
298310
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Mimic.Mixfile do
22
use Mix.Project
33

44
@source_url "https://github.com/edgurgel/mimic"
5-
@version "1.12.0"
5+
@version "2.0.0"
66

77
def project do
88
[

test/dsl_test.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ defmodule Mimic.DSLTest do
99
test "basic example" do
1010
stub(Calculator.add(_x, _y), do: @stub)
1111
expect Calculator.add(x, y), do: x + y
12+
expect Calculator.add(x, y), do: x + y + 1
1213
expect Calculator.mult(x, y), do: x * y
1314

1415
assert Calculator.add(2, 3) == 5
1516
assert Calculator.mult(2, 3) == 6
17+
assert Calculator.add(2, 3) == 6
1618

17-
assert Calculator.add(2, 3) == @stub
19+
assert Calculator.add(5, 3) == @stub
1820
end
1921

2022
test "guards on stub" do

test/mimic_test.exs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ defmodule Mimic.Test do
214214
assert Calculator.add(3, 4) == 7
215215
end
216216

217-
test "invokes stub after expectations are fulfilled" do
217+
test "return stub when all expectations are fulfilled and another call is made" do
218218
Calculator
219219
|> stub(:add, fn _x, _y -> @stubbed end)
220220
|> expect(:add, fn _, _ -> @expected_1 end)
@@ -223,6 +223,7 @@ defmodule Mimic.Test do
223223
assert Calculator.add(1, 1) == @expected_1
224224
assert Calculator.add(1, 1) == @expected_2
225225
assert Calculator.add(1, 1) == @stubbed
226+
226227
verify!()
227228
end
228229

@@ -310,7 +311,7 @@ defmodule Mimic.Test do
310311
assert_receive :ok
311312
end
312313

313-
test "invokes stub after expectations are fulfilled" do
314+
test "raise when all expectations are fulfilled and another call is made" do
314315
Calculator
315316
|> stub(:add, fn _x, _y -> @stubbed end)
316317
|> expect(:add, fn _, _ -> @expected end)
@@ -431,15 +432,42 @@ defmodule Mimic.Test do
431432
verify!(self())
432433
end
433434

434-
test "expecting when no expectation is defined calls original" do
435+
test "raise when all expectations are fulfilled and another call is made" do
436+
Calculator
437+
|> expect(:add, fn x, _y -> x + 2 end)
438+
|> expect(:mult, fn x, _y -> x * 2 end)
439+
440+
assert Calculator.add(4, 0) == 4 + 2
441+
assert Calculator.mult(5, 0) == 5 * 2
442+
443+
message =
444+
~r"Calculator.mult/2 called in process #PID<.*> but expectations are already fulfilled"
445+
446+
assert_raise Mimic.UnexpectedCallError, message, fn -> Calculator.mult(5, 3) end
447+
end
448+
449+
test "expectation can be added after being fulfilled" do
435450
Calculator
436451
|> expect(:add, fn x, _y -> x + 2 end)
437452
|> expect(:mult, fn x, _y -> x * 2 end)
438453

439454
assert Calculator.add(4, 0) == 4 + 2
440455
assert Calculator.mult(5, 0) == 5 * 2
441456

442-
assert Calculator.mult(5, 3) == 15
457+
message =
458+
~r"Calculator.mult/2 called in process #PID<.*> but expectations are already fulfilled"
459+
460+
assert_raise Mimic.UnexpectedCallError, message, fn -> Calculator.mult(5, 3) end
461+
462+
Calculator
463+
|> expect(:mult, fn x, _y -> x * 2 end)
464+
465+
assert Calculator.mult(4, 0) == 4 * 2
466+
467+
message =
468+
~r"Calculator.mult/2 called in process #PID<.*> but expectations are already fulfilled"
469+
470+
assert_raise Mimic.UnexpectedCallError, message, fn -> Calculator.mult(5, 3) end
443471
end
444472

445473
test "raises if a non copied module is given" do
@@ -483,6 +511,14 @@ defmodule Mimic.Test do
483511
assert_receive :ok
484512
end
485513

514+
test "expectation on a function and call others" do
515+
Calculator
516+
|> expect(:add, fn x, _y -> x + 2 end)
517+
518+
assert Calculator.add(4, :_) == 6
519+
assert Calculator.mult(5, 2) == 10
520+
end
521+
486522
test "stacking expectations" do
487523
Calculator
488524
|> expect(:add, fn _x, _y -> @expected_1 end)
@@ -545,7 +581,7 @@ defmodule Mimic.Test do
545581
verify!(self())
546582
end
547583

548-
test "expecting when no expectation is defined calls original" do
584+
test "raise when all expectations are fulfilled and another call is made" do
549585
Calculator
550586
|> expect(:add, fn x, _y -> x + 2 end)
551587
|> expect(:mult, fn x, _y -> x * 2 end)
@@ -556,7 +592,10 @@ defmodule Mimic.Test do
556592
assert Calculator.add(4, :_) == 6
557593
assert Calculator.mult(5, :_) == 10
558594

559-
assert Calculator.mult(5, 3) == 15
595+
message =
596+
~r"Calculator.mult/2 called in process #PID<.*> but expectations are already fulfilled"
597+
598+
assert_raise Mimic.UnexpectedCallError, message, fn -> Calculator.mult(5, 3) end
560599

561600
send(parent_pid, :ok)
562601
end)

0 commit comments

Comments
 (0)