This is a mocking library for test-driven development in Clojure. It is meant to
be used alongside a regular testing framework such as clojure.test.
This library is designed to be compatible with any testing framework. All supported features are described below in the Framework-agnostic usage section. For convenience, special support for clojure.test is also available and described in the Syntactic sugar for clojure.test section.
In order to use mockfn, it's enough to require it in a test namespace.
(:require [me.pmatiello.mockfn.plain :as mfn]
[me.pmatiello.mockfn.matchers :as mfn.m]
...)
This brings mockfn's features into scope for the namespace.
The providing macro replaces functions with mocks. These mocks return
preconfigured values when called with the expected arguments.
(testing "providing"
(mfn/providing [(one-fn) :result]
(is (= :result (one-fn)))))
As demonstrated below, a mock (one-fn) can be configured with different return
values for different arguments.
(testing "providing - one function, different arguments"
(mfn/providing [(one-fn :argument-1) :result-1
(one-fn :argument-2) :result-2]
(is (= :result-1 (one-fn :argument-1)))
(is (= :result-2 (one-fn :argument-2)))))
It's also possible to configure multiple mocks, for multiple functions, at once.
(testing "providing with more than one function"
(mfn/providing [(one-fn :argument) :result-1
(other-fn :argument) :result-2]
(is (= :result-1 (one-fn :argument)))
(is (= :result-2 (other-fn :argument))))))
The verifying macro works similarly, but also defines an expectation for the
number of times a call should be made during the test. A test will fail if this
expectation is not met.
(testing "verifying"
(mfn/verifying [(one-fn :argument) :result (mfn.m/exactly 1)]
(is (= :result (one-fn :argument)))))
Notice that the expected number of calls is defined using a matcher.
The verifying-eventually macro also defines call-count expectations, but this
verification is performed repeatedly, pausing for a specified interval between
checks, until all expectations are met or the maximum number of attempts is
exceeded.
(testing "verifying-eventually"
(mfn/verifying-eventually
{:max-attempts 10 :interval-ms 20}
[(one-fn :argument) :result (mfn.m/exactly 1)]
(future (Thread/sleep 50) (one-fn :argument))))
Mocks can be configured to return a specific value for a range of different arguments through matchers.
(testing "argument matchers"
(mfn/providing [(one-fn (mfn.m/at-least 10) (mfn.m/at-most 20)) 15]
(is (= 15 (one-fn 12 18))))))
Private functions can be mocked by referring to the Var associated with the
symbol of the mocked function.
(testing "providing, private function"
(mfn/providing [(#'pvt-fn) :result]
(is (= :result (#'pvt-fn)))))
(testing "verifying, private function"
(mfn/verifying [(#'pvt-fn :argument) :result (mfn.m/exactly 1)]
(is (= :result (#'pvt-fn :argument)))))
The invoke function allows mocks to dynamically invoke a function with the
received arguments and return the output.
(testing "returns dynamic values"
(mfn/providing
[(one-fn (matchers/any)) (mfn/invoke identity)]
(is (= :x (#'one-fn :x)))
(is (= :y (#'one-fn :y)))))
The same approach can be used to invoke the original implementation to test whether a function has been invoked as expected without overriding its implementation:
(testing "invokes the original implementation"
(mfn/verifying
[(one-fn (matchers/any)) (mfn/invoke one-fn) (matchers/exactly 2)]
(is (= :x (one-fn :x)))
(is (= :y (one-fn :y)))))
Mocks can be configured to throw exceptions when invoked using the raise
function. This can be used to simulate error scenarios in tests.
(testing "throws an exception"
(mfn/providing
[(one-fn) (mfn/raise (ex-info "error!" {}))]
(is (thrown-with-msg? ExceptionInfo #"error!" (one-fn)))))
The me.pmatiello.mockfn.xtras/return-in-order function allows a mock to return
a sequence of values in the order the mock is called. After the last value, it
will continue from the first value in the sequence.
(testing "returns values in order"
(mfn/providing
[(one-fn) (xtras/return-in-order [:a :b :c])]
(is (= [:a :b :c :a :b] (repeatedly 5 one-fn))))
Support
for clojure.test
is provided in the mockfn.clj-test namespace.
(:require [clojure.test :refer :all]
[me.pmatiello.mockfn.clj-test :as mfn]
[me.pmatiello.mockfn.matchers :as mfn.m]
...)
The mockfn.clj-test/deftest and mockfn.clj-test/testing macros replace
clojure.test/deftest and clojure.test/testing and support a flatter (as in
not nested) mocking style using mockfn.clj-test/providing,
mockfn.clj-test/verifying and mockfn.clj-test/verifying-eventually.
(mfn/deftest deftest-with-builtin-mocking
(is (= :one-fn (one-fn)))
(mfn/providing
(one-fn) :one-fn)
(mfn/testing "testing with built-in-mocking"
(is (= :one-fn (one-fn)))
(is (= :other-fn (other-fn)))
(mfn/verifying
(other-fn) :other-fn (mfn.m/exactly 1))))
Note that to leverage the built-in support for mocking in these macros, it's
necessary to use the providing, verifying, and verifying-eventually
symbols provided in the mockfn.clj-test namespace. This namespace also
provides aliases to the invoke and raise functions.
The following matchers are included in mockfn:
| Matcher | Description | Usage |
|---|---|---|
| Generic | ||
any | Matches any value. | (any) |
a | Matches if the actual value is an instance of the expected type. | (a type) |
exactly | Matches if the actual value is equal to the expected value. | (exactly value) |
pred | Matches if the actual value satisfies the provided predicate function. | (pred pred-fn) |
| Presence | ||
some | Matches if the actual value is not nil. | (some) |
null | Matches if the actual value is nil. | (null) |
empty | Matches if the actual value is empty (a collection, a string, etc.). | (empty) |
| Boolean | ||
truthy | Matches if the actual value is truthy. | (truthy) |
falsy | Matches if the actual value is falsy. | (falsy) |
| Numeric | ||
at-least | Matches if the actual value is greater than or equal to the expected value. | (at-least value) |
at-most | Matches if the actual value is less than or equal to the expected value. | (at-most value) |
between | Matches if the actual value is between the lower and upper bounds. | (between lower upper) |
| String | ||
starts-with | Matches if the actual string starts with the expected prefix. | (starts-with prefix) |
ends-with | Matches if the actual string ends with the expected suffix. | (ends-with suffix) |
includes | Matches if the actual string includes the expected substring. | (includes substring) |
regex | Matches if the expected regular expression matches the actual string. | (regex expression) |
| Collection | ||
contains-all | Matches if the actual collection contains all expected values. | (contains values) |
contains-any | Matches if the actual collection contains any expected values. | (contains-any values) |
| Operators | ||
not> | Matches if the actual value does not match the provided matcher. | (not> matcher) |
and> | Matches if the actual value matches all provided matchers. | (and> m1 m2 ...) |
or> | Matches if the actual value matches any of the provided matchers. | (or> m1 m2 ...) |
*> | Matches if the provided matcher matches all remaining arguments. | (*> matcher) |
All matchers above are available in the me.pmatiello.mockfn.matchers
namespace.
While providing, verifying, and verifying-eventually calls can be nested,
all required stubs and expectations for a single mock must be defined within the
same call. Mocking a function in an inner providing, verifying, or
verifying-eventually call will override any definitions made in the outer
scope for the tests being run in the inner scope.
(testing "nested mocks"
(mfn/providing [(one-fn :argument-1) :result-1]
(mfn/providing [(one-fn :argument-2) :result-2
(other-fn :argument-3) :result-3]
(is (thrown? ExceptionInfo (one-fn :argument-1)))
(is (= :result-2 (one-fn :argument-2)))
(is (= :result-3 (other-fn :argument-3))))
(is (= :result-1 (one-fn :argument-1))))))
Information for developing this library.
The following command will execute the unit tests:
% clj -X:test
The following command will build a jar file:
% clj -T:build jar
To clean a previous build, run:
% clj -T:build clean
Before releasing, update the library version in the build.clj file and the release version and date in the changelog.
Make a commit and generate a new tag:
% export VERSION=$(clj -A:build -M -e '(load-file "build.clj") (println build/version)' \
| tail -n-1)
% git commit -a -m "Release: ${VERSION}"
% git tag -a "v${VERSION}" -m "Release: ${VERSION}"
% git push
% git push origin "v${VERSION}"
Run all tests and build the release artifact:
% clj -X:test
% clj -T:build clean
% clj -T:build jar
To release to Clojars, run:
% mvn deploy:deploy-file \
-Dfile=target/mockfn-${VERSION}.jar \
-DrepositoryId=clojars \
-Durl=https://clojars.org/repo \
-DpomFile=target/classes/META-INF/maven/me.pmatiello/mockfn/pom.xml \
-DgeneratePom=false \
-Daether.connector.basic.parallelPut=false
Note that this step requires Clojars to be configured as a server in the local
~/.m2/settings.xml file.
This software is open-source, but closed to contributions.
Distributed under the Eclipse Public License either version 2.0 or (at your option) any later version.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |