Add Travis CI configuration.
[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 [![Build Status](https://travis-ci.org/Ferada/cl-mock.svg?branch=master)](https://travis-ci.org/Ferada/cl-mock)
14
15
16 # INTRODUCTION
17
18 This small library provides a way to replace the actual implementation
19 of either regular or generic functions with mocks.  On the one hand how
20 to integrate this facility with a testing library is up to the user; the
21 tests for the library are written in [`FIVEAM`][2] though, so most
22 examples will take that into account.  On the other hand writing
23 interactions for mocks usually relies on a bit of pattern matching,
24 therefore the regular `CL-MOCK` package relies on [`OPTIMA`][3] to
25 provide that facility instead of deferring to the user.  Should this be
26 a concern a reduced system definition is available as `CL-MOCK-BASIC`,
27 which excludes the definition of `ANSWER` and the dependency on
28 [`OPTIMA`][3].
29
30 Since it is pretty easy to just roll something like this on your own,
31 the main purpose is to develop a nice (lispy, declarative) syntax to
32 keep your tests readable and maintainable.
33
34 Some parts may be used independently of the testing facilities,
35 e.g. dynamic `FLET` may be of general interest.
36
37
38 # MOCKING REGULAR FUNCTIONS
39
40 Let's say we have a function `FOO`, then we can replace it for testing
41 by establishing a new mocking context and then specifying how the new
42 function should behave (see below in **UTILITIES** for a more primitive
43 dynamic function rebinding):
44
45     > (declaim (notinline foo bar))
46     > (defun foo () 'foo)
47     > (defun bar (&rest args)
48     >   (declare (ignore args))
49     >   'bar)
50     > (with-mocks ()
51     >   (answer (foo 1) 42)
52     >   (answer foo 23)
53     >   (values
54     >    (eql 42 (foo 1))
55     >    (eql 23 (foo 'bar))))
56     > => T T
57
58 The `ANSWER` macro has pattern matching (see [`OPTIMA`][3]) integrated.
59 Therefore something like the following will now work as expected:
60
61     > (with-mocks ()
62     >   (answer (foo x) (format T "Hello, ~A!" x))
63     >   (foo "world"))
64     > => "Hello, world!"
65
66 If you don't like `ANSWER` as it is, you can still use `IF-CALLED`
67 directly.  Note however that unless `UNHANDLED` is called, the function
68 always matches and the return value is directly returned again:
69
70     > (with-mocks ()
71     >   (if-called 'foo (lambda (x)
72     >                     (unhandled)
73     >                     (error "Not executed!")))
74     >   (if-called 'foo (lambda (x) (format T "Hello, ~A!" x)))
75     >   (foo "world"))
76     > => "Hello, world!"
77
78 Be especially careful to handle all given arguments, otherwise the
79 function call will fail and that error is propagated upwards.
80
81 `IF-CALLED` also has another option to push a binding to the front of
82 the list, which (as of now) isn't available via `ANSWER` (and should be
83 treated as subject to change anyway).
84
85 Should you wish to run the previously defined function, use the function
86 `CALL-PREVIOUS`.  If no arguments are passed it will use the current
87 arguments from `*ARGUMENTS*`, if any.  Otherwise it will be called with
88 the passed arguments instead.  For cases where explicitely calling it
89 with no arguments is necessary, using `(funcall *previous*)` is still
90 possible as well.
91
92     > (with-mocks ()
93     >   (answer foo `(was originally ,(funcall *previous*)))
94     >   (answer bar `(was originally ,(call-previous)))
95     >   (values
96     >    (foo "hello")
97     >    (bar "hello")))
98     > => (WAS ORIGINALLY FOO) (WAS ORIGINALLY BAR)
99
100 The function `INVOCATIONS` may be used to retrieve all recorded
101 invocations of mocks (so far); the optional argument can be used to
102 filter for a particular name:
103
104     > (with-mocks ()
105     >   (answer foo)
106     >   (foo "hello")
107     >   (foo "world")
108     >   (bar "test")
109     >   (invocations 'foo))
110     > => ((FOO "hello")
111     >     (FOO "world"))
112
113 Currently there are no further predicates to check these values, this is
114 however an area of investigation, so presumably either a macro like
115 [`FIVEAM`][2]s `IS`, or regular predicates could appear in this place.
116
117
118 # EXAMPLES
119
120 The following examples may give a better impression.
121
122 Here we test a particular [`ECLASTIC`][4] method, `GET*`.  In order to
123 replace the HTTP call with a supplied value, we use `ANSWER` with
124 `HTTP-REQUEST` and return a pre-filled stream.  Afterwards both the
125 number of `INVOCATIONS` and the actual returned values are checked.
126
127     (use-package '(#:cl-mock #:fiveam #:eclastic #:drakma #:puri))
128
129     (def-test search.empty ()
130       (let* ((events (make-instance '<type> :type "document" :index "index"
131                                             :host "localhost" :port 9292))
132              (text "{\"took\":3,\"timed_out\":false,\"_shards\":{\"total\":5,\
133     \"successful\":5,\"failed\":0},\"hits\":{\"total\":123,\"max_score\":1.0,\
134     \"hits\":[{\"_index\":\"index\",\"_type\":\"document\",\"_id\":\"12345\",\
135     \"_score\":1.0,\"_source\":{\"test\": \"Hello, World!\"}}]}}")
136              (stream (make-string-input-stream text)))
137         (with-mocks ()
138           (answer http-request
139             (values stream 200 NIL
140                     (parse-uri "http://localhost:9292/index/document/_search")
141                     stream NIL "OK"))
142           (let ((values (multiple-value-list
143                          (get* events (new-search NIL)))))
144             (is (eql 1 (length (invocations))))
145             (is (eql 1 (length (car values))))
146             (is-true (typep (caar values) '<document>))
147             (is (equal (cdr values)
148                        '(NIL (:hits 123
149                               :shards (:total 5 :failed 0 :successful 5)
150                               :timed-out NIL :took 3))))))))
151
152 Of course, running this should produce no errors:
153
154     > (run! 'search.empty)
155     >
156     > Running test SEARCH.EMPTY ....
157     > Did 4 checks.
158     >    Pass: 4 (100%)
159     >    Skip: 0 ( 0%)
160     >    Fail: 0 ( 0%)
161     >
162     > => NIL
163
164
165 # UTILITIES
166
167 `DFLET` dynamically rebinds functions similar to `FLET`:
168
169     > (defun foo () 42)
170     > (defun bar () (foo))
171     > (bar)
172     > => 42
173     > (dflet ((foo () 23))
174     >   (bar))
175     > => 23
176     > (OR) => 42, if FOO was inlined
177
178 The caveat is that this might not work on certain optimisation settings,
179 including inlining.  That trade-off seems acceptable; it would be nice
180 if a warning could be issued depending on the current optimisation
181 settings, however that is particularly implementation dependent, so lack
182 of a warning won't indicate a working environment.
183
184 The underlying function `PROGF` may be used as well similarly to the
185 standard `PROG`:
186
187     > (progf '(foo) (list (lambda () 23))
188     >   (bar))
189     > => 23
190     > (OR) => 42, if FOO was inlined
191
192 [1]: http://common-lisp.net/project/closer/closer-mop.html
193 [2]: http://common-lisp.net/project/fiveam/
194 [3]: https://github.com/m2ym/optima
195 [4]: https://github.com/gschjetne/eclastic