Closed hickscorp closed 4 years ago
Hi @hickscorp! I recommend moving this discussion to the Elixir Forum, as other people can help with their input and feedback and how they tackled this.
Good afternoon @josevalim and thank you for the answer. Doing now.
For historical / documentation purposes, we found a satisfactory way of doing what we were trying to do. See https://elixirforum.com/t/mocking-during-one-test-or-one-test-module-only/29804 for more information.
Basically, we declared a common MyApp.Case
module that uses ExUnit.CaseTemplate
. All our test modules use it now.
The MyApp.Case
is in charge of stubbing all mocks with their default implementation in a setup
block that runs before each test globally, something like this:
defmodule MyApp.Case do
use ExUnit.CaseTemplate
import Hammox, only: [stub_with: 2]
setup _tags do
NormalizerMock |> stub_with(Utils)
...
:ok
end
end
This way, a test that needs to check that a mock is really called once can look like this:
defmodule MyApp.Accounts.UserTest do
use MyApp.Case, async: true
import MyApp.Factory
import Hammox, only: [expect: 3, verify_on_exit!: 1]
doctest User, import: true
setup :verify_on_exit!
describe "changeset/2" do
test "normalizes the email" do
new_email = Faker.Internet.email()
exp = "normalized_#{new_email}"
NormalizerMock
|> expect(:normalize_email, fn ^new_email -> exp end)
%{changes: changes} =
:user
|> build
|> User.changeset(%{email: new_email})
assert changes == %{email: exp}
end
end
end
I hope this will help.
Consider the following scenario:
Normalizer
behaviour with anormalize_email
function.changeset
function that should normalize theemail
param.User.changeset
function calls a default implementation defined such asnormalizer().normalize_email()
-normalizer()
is defined to returnApplication.get_env(:my_app :normalizer, Utils)
. It means that by default there's anormalize_email
function in theUtils
module that does the job.User
module use thenormalizer().normalize_email()
function.mocks.ex
file in thetest/support
folder, weMox.defmock(NormalizerMock, for: Normalizer)
and intest_helper.exs
we simplyApplication.put_env(:my_app :normalizer, NormalizerMock)
.Now in our user tests, this is all nice and dandy - we can
expect
on the mock and make sure that the correct function is called by all theUser
functions such as theUser.changeset
one.The intent is really just to test that the
User.changeset
and other functions delegate the work to theUtils
module - so mocking it gives us a way to verify this. We are not interested in what theUtils.normalize_email
function does from these tests and the only place where theUtils.normalize_email
function is tested is in its ownUtils
tests. In other words we just check that theNormalizerMock.normalize_email
function was called exactly once with a parameter we control, and that the result of theUser.changeset
function includes this controlled parameter in its return.However in other tests that target other files and modules, we would like this mock to be absent completely, and have the
User.changeset
function normally call onto theUtils.normalize_email
function.For example, in tests related to a registration controller (or say more integration tests), we call functions on a
Registration
module, itself calling theUser.changeset
function - and in this case we don't care to haveUser.changeset
call a mock - instead it should call its default implementation. Of course, doing things pixel-perfect would require us to actually mock theUser.changeset
function in theRegistration
tests, but there is too much overhead here.Is there a way to have mocks applied only to one test? To one test file? Where would all the moving pieces go (Eg where would the
defmock
call be, theApplication.put_env
call etc)?