[ANN] Pretend.jl

Hi there,

Pretend.jl is a tool for writing functions that can be mocked easily. For the sake of simplicity, I’m using the word “mock” for all kinds of testing doubles.

Such tool is very useful when working on software that requires external dependencies such as a cloud resource, database, or another micro-service. Testing against these dependencies are often challenging because they may be unavailable at build time or too expensive to be used frequently.

Sample usage

Pretend.activate()   # Turn on the Pretend framework

# Annotate any function with @mockable macro
@mockable add(x, y) = x + y

# Apply a patch
apply(add => (x,y) -> x - y) do
    @test add(1, 2) == -1
end

# Apply a patch conditionally
apply(add => (x,y) -> x == y ? 0 : Fallback()) do
    @test add(1, 2) == 3
    @test add(5, 5) == 0
end

It is possible to spy on calls and verify usage statistics:

# Verification
@mockable foo() = bar(1,2)
@mockable bar(x,y) = x * y
spy() do
    foo()
    @test called_exactly_once(bar, 1, 2)
end

Mocking third-party function is a little tricky because you do not own the function definition. Pretend.jl solves the problem by creating a function in your module with the same name behind the scene.

@mockable Base.sin(x::Real)
fakesin(x::Real) = 10
apply(sin => fakesin) do
    @test sin(1.0) == 10
end

Anonymous function can be mocked via a wrapper:

add_curry(n) = (x) -> x + n
add1 = mocked(add_curry(1))  # function, not macro
apply(add1 => (x) -> x + 10) do
    @test add1(1) == 11
end

Related packages

Mocking.jl has a different design such that the mocks are annotated at the call site rather than at the function definition. I found that somewhat instrusive because it makes the code less readable. And that’s the motivation for developing Pretend.jl.

SimpleMock.jl provides almost the same functionality. It’s a very cool package that uses Cassette.jl’s machinery. Pretend.jl is implemented differently using macros.

12 Likes