Overhaul and version bump.
[cl-mock.git] / README.md
1 -*- mode: markdown; coding: utf-8-unix; -*-
2
3 CL-MOCK - Mocking functions.
4
5 Copyright (C) 2013-14 Olof-Joachim Frahm
6
7 Release under a Simplified BSD license.
8
9 Working, but unfinished.
10
11 Should be portable.
12
13
14 # INTRODUCTION
15
16 This small library provides a way to replace the actual implementation
17 of either regular or generic functions with mocks.  On the one hand how
18 to integrate this facility with a testing library is up to the user; the
19 tests for the library are written in [`FIVEAM`][2] though, so most
20 examples will take that into account.  On the other hand writing
21 interactions for mocks usually relies on a bit of pattern matching,
22 therefore the regular `CL-MOCK` package relies on [`OPTIMA`][3] to
23 provide that facility instead of deferring to the user.  Should this be
24 a concern a reduced system definition is available as `CL-MOCK-BASIC`,
25 which excludes the definition of `ANSWER` and the dependency on
26 [`OPTIMA`][3].
27
28 Since it is pretty easy to just roll something like this on your own,
29 the main purpose is to develop a nice (lispy, declarative) syntax to
30 keep your tests readable and maintainable.
31
32 Some parts may be used independently of the testing facilities,
33 e.g. dynamic `FLET` may be of general interest.
34
35
36 # MOCKING REGULAR FUNCTIONS
37
38 Let's say we have a function `FOO`, then we can replace it for testing
39 by establishing a new mocking context and then specifying how the new
40 function should behave (see below in **UTILITIES** for a more primitive
41 dynamic function rebinding):
42
43     > (declaim (notinline foo bar))
44     > (defun foo () 'foo)
45     > (defun bar () 'bar)
46     > (with-mocks ()
47     >   (answer (foo 1) 42)
48     >   (answer foo 23)
49     >   (values
50     >    (eql 42 (foo 1))
51     >    (eql 23 (foo 'bar))))
52     > => T T
53
54 The `ANSWER` macro has pattern matching (see [`OPTIMA`][3]) integrated.
55 Therefore something like the following will now work as expected:
56
57     > (with-mocks ()
58     >   (answer (foo x) (format T "Hello, ~A!" x))
59     >   (foo "world"))
60     > => "Hello, world!"
61
62 If you don't like `ANSWER` as it is, you can still use `IF-CALLED`
63 directly.  Note however that unless `UNHANDLED` is called, the function
64 always matches and the return value is directly returned again:
65
66     > (with-mocks ()
67     >   (if-called 'foo (lambda (x)
68     >                     (unhandled)
69     >                     (error "Not executed!")))
70     >   (if-called 'foo (lambda (x) (format T "Hello, ~A!" x)))
71     >   (foo "world"))
72     > => "Hello, world!"
73
74 Be especially careful to handle all given arguments, otherwise the
75 function call will fail and that error is propagated upwards.
76
77 `IF-CALLED` also has another option to push a binding to the front of
78 the list, which (as of now) isn't available via `ANSWER` (and should be
79 treated as subject to change anyway).
80
81 The function `INVOCATIONS` may be used to retrieve all recorded
82 invocations of mocks (so far); the optional argument can be used to
83 filter for a particular name:
84
85     > (with-mocks ()
86     >   (answer foo)
87     >   (foo "hello")
88     >   (foo "world")
89     >   (bar "test")
90     >   (invocations 'foo))
91     > => ((FOO "hello")
92     >     (FOO "world"))
93
94 Currently there are no further predicates to check these values, this is
95 however an area of investigation, so presumably either a macro like
96 [`FIVEAM`][2]s `IS`, or regular predicates could appear in this place.
97
98
99 # UTILITIES
100
101 `DFLET` dynamically rebinds functions similar to `FLET`:
102
103     > (defun foo () 42)
104     > (defun bar () (foo))
105     > (bar)
106     > => 42
107     > (dflet ((foo () 23))
108     >   (bar))
109     > => 23
110     > (OR) => 42, if FOO was inlined
111
112 The caveat is that this might not work on certain optimisation settings,
113 including inlining.  That trade-off seems acceptable; it would be nice
114 if a warning could be issued depending on the current optimisation
115 settings, however that is particularly implementation dependent, so lack
116 of a warning won't indicate a working environment.
117
118 The underlying function `PROGF` may be used as well similarly to the
119 standard `PROG`:
120
121     > (progf '(foo) (list (lambda () 23))
122     >   (bar))
123     > => 23
124     > (OR) => 42, if FOO was inlined
125
126 [1]: http://common-lisp.net/project/closer/closer-mop.html
127 [2]: http://common-lisp.net/project/fiveam/
128 [3]: https://github.com/m2ym/optima