Mocking are not inherently bad, they are just a tool, but they are frequently overused. I am writing a post on the topic but generally you could do the following. Imagine this example in Elixir where some function may perform heavy work which you want to isolate in tests:
```elixir
defmodule MyModule do
def my_function do
# ...
SomeDependency.heavy_work(arg1, arg2)
# ...
end
end
```
Your test should never do this:
```elixir
test "my function performs heavy work" do
mock(SomeDependency, :heavy_work, to_return: true)
MyModule.my_function()
end
```
Because that is changing how `SomeDependency` works *globally*, in your whole codebase. This is particular aggravating on Elixir as changing global values means you can no longer run that part of your suite concurrently.
Instead, pass the dependency around. Passing locally can be done in multiple ways. If your dependency surface is tiny, an anonymous function will suffice:
```elixir
defmodule MyModule do
def my_function(heavy_work \\ &SomeDependency.heavy_work/2) do
# ...
heavy_work.(arg1, arg2)
# ...
end
end
```
And in your test:
```elixir
test "my function performs heavy work" do
heavy_work = fn _, _ ->
# Simulate heavy work by sending self() a message
send self(), :heavy_work
end
MyModule.my_function(heavy_work)
assert_received :heavy_work
end
```
Or define the contract, as I explained in the earlier e-mails, and pass a module in:
```elixir
defmodule MyModule do
def my_function(dependency \\ SomeDependency) do
# ...
dependency.heavy_work(arg1, arg2)
# ...
end
end
```
Now in your test:
```elixir
test "my function performs heavy work" do
# Simulate heavy work by sending self() a message
defmodule TestDependency do
def heavy_work(_arg1, _arg2) do
send self(), :heavy_work
end
end
MyModule.my_function(TestDependency)
assert_received :heavy_work
end
```
TestDependency in this example is a mock. Except you define it manually instead of using fancy libraries. If you want to check for arguments, you can hardcode them in the TestDependency:
def heavy_work(:shuold_be_1, :should_be_2) do
send self(), :heavy_work
end
Or even send them as a message so you check later:
def heavy_work(arg1, arg2) do
send self(), {:heavy_work, arg1, arg2}
end
If you can't pass the argument, use the Applicaiton.get_env approach discussed earlier.
Keep in mind, as mentioned before, the issue is in finding the boundaries you are comfortable with. For example, instead of replacing the module that sends the e-mail, you could still do the whole job of rendering the e-mail but, instead of calling something like mailgun to send it, you call another backend that would deliver the message to the test process as shown above. So you'd just replace the backend you use for delivery between dev/test/prod. And one of those backends just send self() a message.