EDIT: SimpleMock.jl allows mocking without needing macros in the live code, unlike Mocking.jl
Let’s say I have 2 modules (e.g. below) and I want to test out ModuleB in an isolated way; i.e. without running the code in ModuleA.
Is there a way to mock out or patch ModuleA when I unit test ModuleB ? If not, what are some alternative testing strategies I could use?
module ModuleA
function f_a()
println(1)
end
end
module ModuleB
import Main: ModuleA
function g()
ModuleA.f_a()
end
end
I’m coming from Python, where it’s common to mock calls from other files and instead just assert that the other file was called, instead of actually calling it.
I would avoid mocking whenever possible. Design your program in a functional style so that any I/O is on the outside and you can test the body of your program without substituting anything. When you need to do I/O to test against a web service, try to use the actual web service’s test endpoint unless it’s too slow. At that point you can make a fake FakeClient that behaves just like RealClient but faster – still saving data, just in memory instead of over the net. Not tracking method calls like some mocks would.
function _input_f()
return data
end
function _output_f(data)
end
function logic(
p1, p2;
inpit_data = _input_f(),
output_f = _output_f()
)
result = do_stuff_without_side_effects(p1, p2, inpit_data)
output_f(result)
end
That’s very easy to test. Are there any disadvantage in that style?
Hello from the future, where this discussion seems to be the #1 Google result for “julia test mock”.
For the three libraries mentioned by @tk3369 above, it looks like only Mocking.jl remains at least a semi-active project. SimpleMock.jl’s README says it’s broken for modern versions of Julia, and Pretend.jl looks like it hasn’t been touched in four years, though that doesn’t necessarily imply there’s anything wrong with it.
Also:
Not all mocking is for network isolation purposes. For me, the biggest use case is something like “conditional verification”, where I want to test ModuleB, which internally calls ModuleA, under the conditional assumption that ModuleA is known to work properly, and/or returns a given result quickly.
In many cases I agree that it’s inappropriate to care how ModuleB achieves its results, it’s a black box, and the fact that it uses ModuleA internally is an implementation detail. But in other cases, for lots of different reasons, it’s very helpful to call that a “grey box” that can be marginalized over for testing purposes.
If ModuleB relies on ModuleA it also relies on the correctness of it, i.e., conditional testing might debugging easier – because you know where to look for the failure – but is not enough in its own.
If the coupling between ModuleB and ModuleA is not as strong in the first place, i.e., it just happens to use some functions from ModuleA, but could just as well work with a different backend, it should probably restructured to rely on an abstract interface instead. Julia is especially nice in this respect and as soon as an abstract interface has been identified mocking becomes trivial – by just implementing some mock code adhering to the interface.
There’s not just one right answer. Sometimes I need to test ModuleB end-to-end, sometimes I need to test it conditionally on what ModuleA is providing, sometimes both.