Closed bundacia closed 6 years ago
@bundacia maybe I am missing where the problem is here, but how are you switching up which implementation of the behaviour you use? What I mean is, for example, you can have this in the application environment:
# In test_helper.exs
Application.put_env(:my_app, :storage_module, MockStorage)
and then you read this at runtime in the modules that use the storage, then you can just switch back to the real storage with a similar call to the one above?
Currently we set these application config values in the config/test.exs
. We could use Application.put_env
to switch between the mock and the "real" implementation, but it's a little messy and requires us to remember to set the env back (so the Mox mock is in place for use by the next tests):
setup_all do
old_storage = Application.get_env(:my_app, :storage_module)
Application.put_env(:my_app, :storage_module, Storage)
on_exit fn ->
Application.put_env(:my_app, :storage_module, old_storage)
end
:ok
end
It works (and is what we're doing now), but this solution seemed both simpler and more flexible. With stub_with
, for example, we could configure the mock to use Storage
but still make custom expectations, allowing us to provide custom behaviour for just one or more functions in the mock or even just assert that a function was called with certain arguments:
MockStorage
|> stub_with(Storage)
|> expect(:fetch_by_id, fn 123 -> %{id: 123, color: "blue"})
# or
MockStorage
|> stub_with(Storage)
|> expect(:fetch_by_id, fn 123 -> Stroage.fetch_by_id(123))
Does that make sense?
Another advantage of not swapping the environment variable is that you can still have concurrent tests.
Fair enough. I still am not a fan of having stub_with(MockStorage, Storage)
and then overriding single functions though, it feels weird but I can't articulate why, so not a strong 👎 :)
@josevalim, right. That too.
@whatyouhide, yeah, I don't have a current use case for the "stub the whole thing than add an expectation", so I can't defend that particular case very strongly, it's just something that we're getting for free with stub_with
. The primary value for me here is that this is a little simpler than messing with put_env
and seems a more appropriate abstraction (and is safe to do concurrently).
Also, thanks for the refactor! I'm new to elixir and didn't know about :erlang.make_fun
or function_exported?
, so that was very educational =)
When using Mox it can be hard, once we're using a mock in the test environment, to replace the mock with the "real" implementation in order to do any type of integration testing. For example, say we have an umbrella app with a
storage
app and aweb
app.web
provides a web interface and communicates tostorage
via a defined behaviour to access the data layer. In order to unit testweb
we use Mox to replaceStorage
withMockStorage
. But we also want to have a few integration tests that verify the integration of these two apps. The problem is that onceMockStorage
is in place we can't switch back toStorage
. We need a way to stubMockStorage
and tell it "act just like the realStorage
in this test", without having to explicitly stub every function in the behaviour like this:This pr adds a
stub_with/2
function toMox
that provides that behaviour, allowing us to just say:stub_with
finds all of the behavioursMockStroage
andStroage
have in common and stubs all of those functions with the implementations fromStroage
.