-
Notifications
You must be signed in to change notification settings - Fork 77
Description
Hi @josevalim, hope you are doing well! As usual, thanks for Mox and all the work you do ❤️
I was wondering if we could update the docs on the stub/3 function. After working in a few large Elixir codebases, I’ve noticed a recurring pattern: lingering stub/3 calls that can safely be removed from tests without changing behavior.
From what I've seen, folks reach for stub/3 because it's an overloaded term in testing, much like mock (as shown in that article you wrote back in the day -> https://dashbit.co/blog/mocks-and-explicit-contracts, best in class by the way). Folks are well meaning, but the end result seems to always the same:
- The test includes a
stub/3that never gets used after refactors - When removed, nothing fails
- More importantly, there’s a missed opportunity to use expect/3, which in most cases would have been the right tool
So far, the reasons for this happening has been mostly because:
- The term “stub” feels familiar from other libraries and languages
- Many users don’t immediately notice or understand
expect/3as the verification-based alternative and how powerful it is
Once I show how a certain stub call can be removed and mention expect/3, it kinda clicks for them.
Have you seen this? Would this diff be of value in your opinion? I have this commited and can push it up if you think it adds value.
diff --git a/lib/mox.ex b/lib/mox.ex
index 2ae708b..2bba59f 100644
--- a/lib/mox.ex
+++ b/lib/mox.ex
@@ -597,6 +597,17 @@ defmodule Mox do
stub(MockWeatherAPI, :get_temp, fn _loc -> {:ok, 30} end)
`stub/3` will overwrite any previous calls to `stub/3`.
+
+ > #### ⚠️ Warning {: .warning}
+ >
+ > `stub/3` does **not verify that a call occurred**.
+ > If the production code stops calling the stubbed function
+ > (for example, after a refactor), the test will still pass.
+ >
+ > This can lead to unused stubs lingering in tests and a
+ > false sense of coverage. Use `expect/3` instead when you
+ > want to ensure the function is actually invoked, which is
+ > the preferred approach in most cases.
"""
@spec stub(mock, atom(), function()) :: mock when mock: t()
def stub(mock, name, code)Resulting in this:
Thanks!
Paulo