Question on mocking of arbitrary function for testing

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.

(note, this is “first steps” question)

Thank you very much,

There is a SimpleMock.jl which can do what you want. Unfortunately it is rather limited in functionality and broken on Julia nightly as far as I know.

1 Like

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)

Thanks a lot for helping!

1 Like

You could also try my Pretend.jl package.

https://github.com/tk3369/Pretend.jl

1 Like

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.

Fair enough :slightly_smiling_face:

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