belittle - /bɪˈlɪt(ə)l/ - To denigrate, make small of, to mock.
A Clojure library that aims to bring mocking to core.test, with enough flexibility to be used with test.check and extensibility so you can extend to meet your needs.
(:require [clojure.test :refer :all]
[belittle.core :refer :all])
(def incer inc)
(def decer dec)
(def get-decer-0-1
{(m decer 0) 1})
(deftest simple-mock
(given
{(incer 0) 2}
(is
(= (incer 0) 2))))
(deftest merged-mock
(given (merge {(incer 0) 2}
decer-0-1)
(is (= (incer 0)
2)
(= (decer 0)
1))))
The main contribution is the given
macro. Fed a map of function calls to a value it rebinds the functions to mocks that return the provided value. Arg matching is provided, so only the specified args will be accepted. Arg's can also be functions, so predicates can be used instead of values. Return values can be Mock instances, to allow for restricted or changable responses.
Before given
rebinds the vars it's first arg is evaluated. This allows the mocking map to be the product of a merge
call, thereby allow mocks to be paramatised, grouped and resused for multiple tests. The macro m
is provided to prevent evaluation of calls in mock producing functions.
Mocking is encapsulated by the Mock
protocol that defines a respond
and complete
function. The library will aim to provide easy access to some basics (once
, never
, etc), but this is the extension point for any custom mocking needs you may have. complete
is called on mocks after the tests assertions, Mock
implementations are expected to call core.test/do-report
with their status.
Besides creating a new way of structuring mock heavy tests, this libraries key motivation is to explore the combination of generative testing and mocking. For the best experience it is highly recommended to use the core.test integration provided by test.chuck. This prevents test failures being reported until after the shrinking process has been completed.
To see this combination in action see the generative test namespace.
For the following examples the following is defined, tests pass unless otherwise specified:
(:require [clojure.test :refer :all]
[belittle.core :refer :all])
(def incer inc)
(def decer dec)
(def decer-0-1
{(m decer 0) 1})
(defn get-decer
([{:keys [x y]}]
(get-decer x y))
([x y]
{(m decer x) y}))
Here you can see the use of m
to prevent the decer
expressions being evaluated too early, m
simply calls var
on the first element and returns a list of the var and the args.
Let's bind incer, when called with 0, to return 2.
(deftest simple-mock
(given
{(incer 0) 2}
(is
(= (incer 0) 2))))
Let's pull in our fixed mock for decer.
(deftest fixed-mock
(given (merge {(incer 0) 2}
decer-0-1)
(is (= (incer 0)
2)
(= (decer 0)
1))))
Let's adjust our decer mock on the fly.
(deftest dynamic-mock
(given (merge {(incer 0) 2}
(get-decer 0 1))
(is (= (incer 0)
2)
(= (decer 0)
1))))
Let's check the mocks args can be replaced with predicates.
(deftest predicate-mock
(given
{(incer anything) 2}
(is
(= (incer "foo") 2))))
Okaydoke, what about some return constraints.
(deftest under-call
(given
{(incer 0) (once 2)}))
The following is reported:
Mock under called for #'belittle.core-test/incer
expected: 1
actual: 0
Hows about I over call something.
(deftest over-call
(given
{(incer 0) (once 2)}
(is
(= (incer 0) 2))
(is
(= (incer 0) nil))))
Reports:
Mock over called for #'belittle.core-test/incer
expected: 1
actual: 2
Only one failure is reported here as when over called the mock returns nil
, so the second assertion passes in this example, the mock only reports the failure when complete
is called.
Belittle aims to be simple to use for simple use cases, and able to help for complex ones. To this end mocks can be composed together. When creating a mock, if belittle encounters a non-mock element it is wrapped to fit in. Hence the following are equilivent, in fact the first becomes the second:
{(incer 0) 2}
{(incer 0) (any-times (returning 2))}
As this shows call counting is decomplected from what to return. So you can do stuff like:
(thrice (stream (cons (throwing (new Exception)) (repeat 2))))
stream
is a returning mock that works it's way through a collection of responses. The responses are laziness wrapped with returning
if they are not already a mock, hence 2
can just be a plain symbol.
Copyright © 2016 Tom Coupland
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.