Is there a mocking/stubbing framework for Common Lisp?
Asked Answered
H

7

12

Is there a mocking/stubbing framework for Common Lisp?

EmacsLispMock looks great, but it is an Emacs lisp framework, and I'm looking for something to use from Common Lisp.

Any suggestions?

Hoxie answered 1/11, 2010 at 11:11 Comment(6)
What does a mocking/stubbing framework do?Seidler
@Seidler The idea is to let you test a given function in isolation, by controlling the behaviour of other functions. So if you have a function A that calls a function B, you can stub B to always return 5, or something, and verify that A does what it is supposed to do with that return value. That way you can verify that A works without having to call the actual B. A common scenario is testing code that depends on database access, without having to set up and configure a database for each test.Selectman
I'd probably just define a "stub function B" as (defun b (&rest args) 5) if I wanted, specifically, have a function returning 5. ONce that's in place and functions using my "B" have been tested, reload the proper definition.Vitebsk
@vatine That would be useful during development, if you want to implement A before B, but if you then redefine B with the proper implementation you lose the ability to perform isolated regression tests for A. For example, if A should be able to handle integers and nil from B, I would like regression tests for both these cases that are isolated from the actual implementation of B (since it can be slow and difficult to control if it relies on the file system, a database etc).Selectman
If you do something like (defvar b-return-value), then define b as (defun b (&rest args) b-return-value)), you can wrap a LET, re-binding b-return-value. Still means you need to have one definition for testing and one for actual operation, though.Vitebsk
Fellow googlers: we have cl-mock and mockingbird both in Quicklisp.Backing
B
5

The following should do what you're looking for

(defmacro with-replaced-function (fdef &rest body)
  (let ((oldf (gensym))
        (result (gensym))
        (name (car fdef))
        (args (cadr fdef))
        (rbody (cddr fdef)))
    `(let ((,oldf (symbol-function ',name)))
       (setf (symbol-function ',name) (lambda ,args ,@rbody))
       (let ((,result (progn ,@body)))
         (setf (symbol-function ',name) ,oldf)
         ,result))))

(defmacro show (x)
  `(format t "~a --> ~a~%"
           ',x ,x))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun foo (x y) (+ x y))

(defun bar (x) (foo x (* x 2)))

(show (bar 42))

(show (with-replaced-function (foo (x y) (* x y))
                              (bar 42)))

(show (bar 42))

The macro simply saves the function being currently pointed by the symbol and replaces it with the provided stub implementation. At the end of the block the function is restored to the original value.

It would probably make sense to add a protection against non-local exits from the body.

Note also that obviously changing a function definition is not going to work if function calls have been inlined by a compiler. CL has a special NOTINLINE declaration that can be used to prevent this problem.

Botelho answered 3/11, 2010 at 9:38 Comment(2)
It should be noted that this might not necessarily work in compiled code, as functions might be inlined by the file compiler (the compiler can for example assume that functions in the same file stay the same). So it might be necessary to declare these functions to be not inlined.Switchblade
Protection against non-local exits is done using unwind-protect. That's a quite basic pattern, often found in macros.Gunning
B
3

A few years later, there is. We have cl-mock and mockingbird both in Quicklisp.

(ql:quickload :mockingbird)

This one also allows to check if a function was called, if so how many times and with which arguments and it's possible to stub individual methods.

Backing answered 19/9, 2017 at 22:29 Comment(0)
N
2

As Rainer points out, the file compiler sometimes inlines functions, which means changing the function definition won't have any effect in the places where the function was inlined.

Changing the function name's definition also won't replace use of the function as a literal object, for example, if you saved #'my-stubbed-function in a variable somewhere.

However, in some lisp implementations you can define function wrappers or use advice to achieve this: For example, Allegro CL has fwrappers. In SBCL, you could use TRACE with a nonstandard :break function and a custom *invoke-debugger-hook*, and I'm sure other lisp implementations will have something similar.

I don't think anyone has packaged these stubbing methods into a library. You could be the first! (And it would be awesome. I'd love to use something like this.)

Nose answered 18/11, 2010 at 6:12 Comment(0)
C
1

You don't need a mocking/stubbing framework in CL.

Just create new CLOS derived from your class class with methods ovverides for what you want to stub/mock and you are done.

As for stubbing, why not just redefine function?

Chagrin answered 1/11, 2010 at 13:7 Comment(3)
Thanks for your answer. I'm also looking for a solution that would work with code that does not use CLOS. I would like to be able to stub basically any function that calls another function.Selectman
I believe that FLET only binds a function to a symbol within a specified lexical context.Vitebsk
Redefining the function would work if there is a good way to "restore" the definition afterwards, since I would like to be able to run the tests without messing up the actual function definitions. Any ideas on that?Selectman
A
1

Ain't this simplest way to do this?

> (defun b () 'original)
B
> (setf f #'b)
#<Compiled-function B #xC2C1546>
> (defun a () (funcall f))
A
> (a)
ORIGINAL
> (setf f #'(lambda () 'stub))
#<Anonymous Function #xC2D990E>
> (a)
STUB
> (setf f #'b)
#<Compiled-function B #xC2C1546>
> (a)
ORIGINAL
Allwein answered 2/11, 2010 at 5:19 Comment(1)
Interesting suggestion. So, it would mean that you have to write your code with certain function calls that are meant to be replacable, by using funcall rather than an ordinary call. Maybe that is the common lisp way? I.e. the implementation to use can vary during runtime, hence use funcall.Selectman
T
1

I wrote a library with a macro very similar to @6502's answer (with-mocked-functions), but a little more general. It also provides with-added-methods which allows you to write mock methods for a limited dynamic scope. You can find it here: https://github.com/bytecurry/bytecurry.mocks

Thermoplastic answered 5/4, 2015 at 4:6 Comment(0)
C
0

You can try to wrap function re-definition inside a macro

(defmacro with-fun (origfn mockfn &body body)
  `(let ((it ,origfn))
      (setf ,origfn ,mockfn)
     ,@body
      (setf ,origfn ,it)))

This is just an idea, and you will have to implement such macro. You can google for profile implementation which does exactly that, replace one function with another and add profiling information. You can borrow some ideas from there.

Chagrin answered 2/11, 2010 at 21:15 Comment(2)
Looks sweet! I'll definitely give it a trySelectman
For the newbies looking for answers: This is a anaphoric macro and your code will not work if you pass a body that contains the symbol it. For more information check out: en.wikipedia.org/wiki/Anaphoric_macro and en.wikipedia.org/wiki/Hygienic_macroChas

© 2022 - 2024 — McMap. All rights reserved.