https://github.com/pmatiello/mockfn
A mocking library for Clojure.
https://github.com/pmatiello/mockfn
clojure mocking testing
Last synced: 12 months ago
JSON representation
A mocking library for Clojure.
- Host: GitHub
- URL: https://github.com/pmatiello/mockfn
- Owner: pmatiello
- License: epl-2.0
- Created: 2024-10-19T01:40:14.000Z (over 1 year ago)
- Default Branch: master
- Last Pushed: 2025-04-29T18:14:15.000Z (about 1 year ago)
- Last Synced: 2025-06-05T12:09:24.708Z (about 1 year ago)
- Topics: clojure, mocking, testing
- Language: Clojure
- Homepage:
- Size: 143 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# me.pmatiello/mockfn
This is a library for mockist test-driven-development in Clojure. It is meant to
be used alongside a regular testing framework such as `clojure.test`.
[](https://clojars.org/me.pmatiello/mockfn)
[](https://cljdoc.org/d/me.pmatiello/mockfn)
## Usage
Instructions for using this library.
### Framework-agnostic usage
In order to use `mockfn`, it's enough to require it in a test namespace.
```clj
(:require [me.pmatiello.mockfn.plain :as mfn]
[me.pmatiello.mockfn.matchers :as mfn.m]
...)
```
This will bring `mockfn` features in scope for this namespace.
#### Stubbing Function Calls
The `providing` macro replaces functions with mocks. These mocks return
preconfigured values when called with the expected arguments.
```clj
(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.
```clj
(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.
```clj
(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))))))
```
#### Verifying Interactions
The `verifying` macro works similarly, but also defines an expectation for the
number of times a call should be performed during the test. A test will fail if
this expectation is not met.
```clj
(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](#built-in-matchers).
#### Argument Matchers
Mocks can be configured to return a specific value for a range of different
arguments through [matchers](#built-in-matchers).
```clj
(testing "argument matchers"
(mfn/providing [(one-fn (mfn.m/at-least 10) (mfn.m/at-most 20)) 15]
(is (= 15 (one-fn 12 18))))))
```
#### Mocking private functions
Private functions can be mocked by referring to the `Var` associated with the
symbol of the mocked function.
```clj
(testing "providing, private function"
(mfn/providing [(#'pvt-fn) :result]
(is (= :result (#'pvt-fn)))))
```
```clj
(testing "verifying, private function"
(mfn/verifying [(#'pvt-fn :argument) :result (mfn.m/exactly 1)]
(is (= :result (#'pvt-fn :argument)))))
```
#### Returning dynamic values
The `invoke` function allows mocks to dynamically invoke a function with the
received arguments and return the output.
```clj
(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:
```clj
(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)))))
```
#### Throwing exceptions
Mocks can be configured to throw exceptions when invoked using the `raise`
function. This can be used to simulate error scenarios in tests.
```clj
(testing "throws an exception"
(mfn/providing
[(one-fn) (mfn/raise (ex-info "error!" {}))]
(is (thrown-with-msg? ExceptionInfo #"error!" (one-fn)))))
```
### Syntax sugar for clojure.test
Support
for [clojure.test](https://clojure.github.io/clojure/clojure.test-api.html)
is provided in the `mockfn.clj-test` namespace.
```clj
(: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` and
`mockfn.clj-test/verifying`:
```clj
(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` and `verifying` versions provided in the
`mockfn.clj-test` namespace.
### Built-in Matchers
The following matchers are included in `mockfn`:
| Matcher | Description | Usage |
|----------------|------------------------------------------------------------------------|-------------------------|
| **Generic** | | |
| `any` | Always matches. | `(any)` |
| `a` | Matches if actual value is an instance of the expected type. | `(a type)` |
| `exactly` | Matches if actual value is equal to the expected value. | `(exactly value)` |
| `empty` | Matches if the actual value is empty. | `(empty)` |
| `pred` | Matches if the actual value satisfies the provided predicate function. | `(pred pred-fn)` |
| **Boolean** | | |
| `truthy` | Matches if the actual value is truthy. | `(truthy)` |
| `falsy` | Matches if the actual value is falsy. | `(falsy)` |
| **Numeric** | | |
| `at-least` | Matches if actual value is greater or equal than the expected value. | `(at-least value)` |
| `at-most` | Matches if actual value is less or equal than the expected value. | `(at-most value)` |
| `between` | Matches if actual value is between the lower and upper bounds. | `(between lower upper)` |
| **String** | | |
| `starts-with` | Matches if actual string starts with the expected prefix. | `(starts-with prefix)` |
| `ends-with` | Matches if actual string ends with the expected suffix. | `(ends-with suffix)` |
| `includes` | Matches if 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 ...)` |
All matchers above are available in the `me.pmatiello.mockfn.matchers`
namespace.
## Quirks and Limitations
While `providing` and `verifying` calls can be nested, all required stubs and
expectations for a single mock must be defined in the same call. Mocking a
function in an inner `providing` or `verifying` call will override any
definitions made in the outer scope for the tests being run in the inner scope.
```clj
(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))))))
```
## Development
Information for developing this library.
### Running tests
The following command will execute the unit tests:
```
% clj -X:test
```
### Building
The following command will build a jar file:
```
% clj -T:build jar
```
To clean a previous build, run:
```
% clj -T:build clean
```
### Releasing
Before releasing, update the library version in the [build.clj](./build.clj)
file.
Make a commit and generate a new tag:
```
% git commit -a -m "Release: ${VERSION}"
% git tag -a "v${VERSION}" -m "Release: ${VERSION}"
% git push
% git push origin "v${VERSION}"
```
To release to [clojars](https://clojars.org), 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
```
Notice that this step requires clojars to be configured as a server in the local
`~/.m2/settings.xml` file.
## Contribution Policy
This software is open-source, but closed to contributions.
## License
Distributed under the Eclipse Public License either version 2.0 or (at your
option) any later version.