-
Notifications
You must be signed in to change notification settings - Fork 45
Mimic.calls/3 to list args from each call #94
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
Conversation
This introduces arg tracking for Mimic so that tests can examine args passed into mocks. This allows for more flexibility in writing certain types of tests, or in migrating from other mock libraries that offer the same affordances.
@edgurgel what do you think about this? I just went through the issues seeing if someone proposed this cause I wanted to propose it myself, and just noticed the awesome Brent (who is a coworker of mine) had the same thought and did all the work 😄 |
Hey team I hate to be a party pooper but I don't feel this belongs inside Mimic. Mimic like Mox expects (no pun intended) that users rely on the anonymous functions to match/assert etc. Most of what One could rely on this custom Mix.install([{:mimic, "~> 1.0"}])
Application.ensure_all_started(:mimic)
# Don't do this at home! Copying an Elixir module for the sack of brevity
# Using an Agent but it could be an ETS table instead for better performance
{:ok, _pid} = Agent.start_link(fn -> %{} end, name: MimicCase)
Mimic.copy(URI)
ExUnit.start()
defmodule MimicCase do
use ExUnit.CaseTemplate
# Clean up after each test
setup do
caller = self()
on_exit fn ->
Agent.update(__MODULE__, fn state -> Map.delete(state, caller) end)
end
:ok
end
using do
quote do
import Mimic, except: [expect: 3]
import MimicCase, only: [expect: 3, calls: 3]
end
end
def expect(module, fn_name, func) do
arity = Function.info(func)[:arity]
arg_list = 1..arity |> Enum.map(&"arg#{&1}") |> Enum.join(", ")
caller = self()
fun_cmd =
"fn(" <> arg_list <> ") -> MimicCase.store_call(caller, module, fn_name, arity, [" <> arg_list <> "]) ; func.(" <> arg_list <> ") end"
{lambda, _} = Code.eval_string(fun_cmd, func: func, caller: caller, module: module, arity: arity, fn_name: fn_name)
Mimic.expect(module, fn_name, lambda)
end
defp call_history(state, caller, module, fn_name, arity) do
get_in(state, [Access.key(caller, %{}), {module, fn_name, arity}])
end
def store_call(caller, module, fn_name, arity, args) do
Agent.update(__MODULE__, fn state ->
call_history = call_history(state, caller, module, fn_name, arity) || []
put_in(state,
[Access.key(caller, %{}), {module, fn_name, arity}],
[
args | call_history
])
end)
end
def calls(module, fn_name, arity) do
caller = self()
Agent.get(__MODULE__, fn state ->
state
|> get_in([caller, {module, fn_name, arity}])
|> Enum.reverse()
end)
end
end
defmodule CalculatorTest do
use MimicCase
test "greets the world" do
expect(URI, :decode_query, fn _, _, _ -> "decoded1" end)
expect(URI, :decode_query, fn _, _, _ -> "decoded2" end)
assert URI.decode_query("percent=oh+yes%21", %{}, :rfc3986) == "decoded1"
assert URI.decode_query("q=example", %{}, :rfc3986) == "decoded2"
assert calls(URI, :decode_query, 3) == [
["percent=oh+yes%21", %{}, :rfc3986],
["q=example", %{}, :rfc3986]
]
end
end
|
That's not really the point though. Sometimes, your use case is: test "foo" do
expect(MyMod, :my_fun, fn -> ... end)
some_state_i_only_know_about_after_calling_some_code = call_some_code()
end You cannot perform assertions on I do see the point in philosophically not wanting to do this though. I still think that |
Right! It would be interesting to see a "real world" example as I personally haven't needed to reach out to Agents with mimic expectations. Do you mind sharing an example of a test? |
stub(Events, :log, fn event, state ->
# Apply a delay to the final event log action for the first batch step
# to cause the second batch step to hit the timeout when attempting
# to acquire a lock
if some_condition() do
Process.sleep(1500)
end
send(test_pid, {:log, event, state})
:ok
end)
assert {:ok, %{id: workflow_run_id}} = call_under_test()
assert_receive {:log, %Events.Evt1{attempt: 1}, %{id: ^workflow_run_id}}
assert_receive {:log, %Events.Evt2{}, %{id: ^workflow_run_id}} Very simplified example, but pretty much what I was talking about above. |
Ok I see what you mean now. Thanks for providing the example. I think that's a fair addition to the library then 👍 I think we need to work on two things then:
Thanks @brentjanderson for starting this! |
Yes I agree that
|
Why would we bother popping the calls? I think it's fine to just return them. |
@edgurgel I think it would be a slightly more intuitive API to "pop" the calls, because it's easy enough to do calls1 = pop()
# ...
calls2 = pop()
calls = calls1 ++ calls2 but kind of nastier to do calls1 = calls()
# ...
calls2 = calls() -- calls1 # ? doesn't necessarily work
calls2 = Enum.drop(calls(), length(calls1)) # meh? |
@whatyouhide oh I see your point, now! For users that only want to get it once popping won't matter. For users that request more than once they can see the difference between those 2 moments. Fair enough 👍 Good point |
Thanks, team! Should release a new version soon. |
@edgurgel looking forward to it! Let me know if I can help in any way, I’m really excited for this to be released 🙃 |
@whatyouhide @brentjanderson 1.12.0 is out! Thanks, team! 🎉 |
This introduces arg tracking for Mimic so that tests can examine args passed into mocks. This allows for more flexibility in writing certain types of tests, or in migrating from other mock libraries that offer the same affordances.