As I understand, Mocking package is mostly used for this. Something like:

using Mocking
function foo()
@mock(bar())

Is there a way to avoid @mock? It looks a bit artificial. My understanding is that it is easy to just redefine function in unit test to what I want, but I cannot find a way how to restore original.

I looked over this SimpleMock.jl and found it relies on Cassette.jl, which is just what I wanted:

So this below example does replaces sin function with cos:

using Cassette: prehook, @overdub, @context
@context Sin2Cos
Cassette.overdub(::Sin2Cos, ::typeof(sin), x) = cos(x)
function myprogram()
alpha = 0.0
println("sin($(alpha)) is $(sin(alpha))")
println("cos($(alpha)) is $(cos(alpha))")
end
myprogram()
println("Now, changing sin to cos:")
Cassette.overdub(Sin2Cos(), myprogram)

Thank you very much for reply. Yet, this approach uses idea that “any function that may need to be mocked should be prefixed with @mockable” which I wanted to avoid.

This is my first experiments with this sort of tooling, so it can be suboptimal, but instead of Cassette.jl one can use IRTools.jl.

Considering the same function myprogram

function myprogram()
alpha = 0.0
println("sin($(alpha)) is $(sin(alpha))")
println("cos($(alpha)) is $(cos(alpha))")
end

One can either work with IR directly

using IRTools: IR, xcall, isexpr
@dynamo function sin2cos(a...)
ir = IR(a...)
ir === nothing && return
for (x, stmt) in ir
isexpr(stmt.expr, :call) || continue
fn = stmt.expr.args[1]
if fn isa GlobalRef && fn.name == :sin
ir[x].expr.args[1] = GlobalRef(Base, :cos)
else
ir[x] = xcall(sin2cos, stmt.expr.args...)
end
end
return ir
end
julia> sin2cos() do
myprogram()
end
sin(0.0) is 1.0
cos(0.0) is 1.0

or even better, use multiple dispatch

using IRTools: IR, recurse!
mock(::typeof(sin), x) = cos(x)
@dynamo function mock(a...)
ir = IR(a...)
ir === nothing && return
recurse!(ir)
return ir
end
julia> mock() do
myprogram()
end
sin(0.0) is 1.0
cos(0.0) is 1.0

and of course it can substitute any number of function calls simultaneously

mock(::typeof(sin), x) = cos(x)
mock(::typeof(cos), x) = sin(x)
julia> mock() do
myprogram()
end
sin(0.0) is 1.0
cos(0.0) is 0.0