Coconut
Coconut is a testing framework for Clojure and ClojureScript. It aims to have a BDD-style syntax à la RSpec or Speclj, support for synchronous as well as asynchronous tests, easily-understood output, Hamcrest style matchers, and a way to easily define your own custom matchers.
Documentation
https://www.coconut-framework.com/
Documentation is available at the above link. The entire public API for coconut is in the coconut.alpha
namespace and should be adequately documented via docstrings. You can also invoke (coconut.alpha/explore)
from the REPL to get a list of what's available.
Disclaimer
I use this on my personal projects. I would not use this for client projects and neither should you! Use something more stable such as clojure.test, Speclj, or Midje.
Getting Started
To use Coconut you will need Java 8 and Clojure 1.9 or greater.
-
Add the library to your list of dependencies
[com.coconut-framework/coconut "x.x.x"]
compile "com.coconut-framework:coconut:x.x.x"
<dependency> <groupId>com.coconut-framework</groupId> <artifactId>coconut</artifactId> <version>x.x.x</version> </dependency>
-
Write some tests
(ns foo.core-test (:require [coconut.alpha :as c])) (c/describe #'+ (c/it "returns the sum of two numbers" (c/assert-that (+ 1 2) (c/is 3))))
REPL
-
Create a main function in your REPL namespace
(ns foo.repl (:require [coconut.alpha :as c])) (defn run-tests [] (c/run {:reporters [:progress :results] :output :cli :criteria [:all]}))
-
Require your test and run it
(require 'foo.core-test) (run-tests)
Leiningen
Coconut does not currently have a Leiningen plugin. It's simple to create your own task, though. Something like this should do the trick...
(ns leiningen.coconut
(:require
[leiningen.core.eval :as lce]
))
(defn coconut
([project & args]
(lce/eval-in-project project
'(do (with-out-str (ctnr/refresh))
(let [result (c/run {:reporters [:progress :results]
:output :cli
:criteria [:and
[:namespace-matches #"coconut.*-test"]
[:not [:has-tag :fixture]]]})]
(if (:coconut.alpha/success? result)
(System/exit 0)
(System/exit 1))))
'(do (require '[clojure.tools.namespace.repl :as ctnr])
(require '[coconut.alpha :as c])))))
Examples
Synchronous Tests
(require '[coconut.alpha :as c])
(c/deftest "+ returns the sum of two numbers"
(c/assert-that (+ 1 2) (c/is 3)))
(c/describe "+"
(c/it "returns the sum of two numbers"
(c/assert-that (+ 1 2) (c/is 3))))
Asynchronous Tests
(require '[coconut.alpha :as c])
(c/deftest "+ returns the sum of two numbers"
{:asynchronous {:timeout 250}}
(future
(c/assert-that (+ 1 2) (c/is 3))
(c/done)))
(c/deftest* "+ returns the sum of two numbers"
{:asynchronous {:timeout 250}}
(fn [{:keys [::c/assert-that ::c/done]}]
(future
(assert-that (+ 1 2) (c/is 3))
(done))))
(c/describe "+"
(c/it "returns the sum of two numbers"
{:asynchronous {:timeout 250}}
(future
(c/assert-that (+ 1 2) (c/is 3))
(c/done)))
(c/it* "returns the sum of three numbers"
{:asynchronous {:timeout 250}}
(fn [{:keys [::c/assert-that ::c/done]}]
(future
(assert-that (+ 1 2 3) (c/is 6))
(done)))))
(c/describe "+"
{:asynchronous {:timeout 250}}
(c/it "returns the sum of two numbers"
(future
(c/assert-that (+ 1 2) (c/is 3))
(c/done)))
(c/it* "returns the sum of three numbers"
(fn [{:keys [::c/assert-that ::c/done]}]
(future
(assert-that (+ 1 2 3) (c/is 6))
(done)))))
Before/After Each
(require '[coconut.alpha :as c])
(def state
(atom 0))
(c/describe "+"
(c/before-each
(fn []
(reset! state 42)))
(c/after-each
(fn []
(reset! state 0)))
(c/it "returns the sum of two numbers"
(c/assert-that (+ @state @state)
(c/is 84))))
Before/After All
(require '[coconut.alpha :as c])
(def state
(atom 0))
(c/describe "+"
(c/before-all
(fn []
(reset! state 42)))
(c/after-all
(fn []
(reset! state 0)))
(c/it "returns the sum of two numbers"
(c/assert-that (+ @state @state)
(c/is 84))))
Around Each
(require '[coconut.alpha :as c])
(def ^{:dynamic true} *n*)
(c/describe "+"
(c/around-each
(fn [it]
(binding [*n* 42]
(it))))
(c/it "returns the sum of two numbers"
(c/assert-that (+ *n* *n*)
(c/is 84))))
(require '[coconut.alpha :as c])
(def state
(atom 0))
(c/describe "+"
(c/around-each
(fn [it]
(reset! state 42)
(it (fn []
(reset! state 0)))))
(c/it "returns the sum of two numbers"
(c/assert-that (+ @state @state)
(c/is 84))))
Environments
(require '[coconut.alpha :as c])
(c/describe "+"
(c/environment
(fn [e]
(assoc e :number 42)))
(c/it "returns the sum of two numbers"
(c/assert-that (+ (:number (c/current-environment))
(:number (c/current-environment)))
(c/is 84)))
(c/it* "returns the sum of two numbers"
(fn [{:keys [::c/assert-that :number]}]
(assert-that (+ number number)
(c/is 84)))))
Exploring the API
(require '[coconut.alpha :as c])
(c/explore) ;;=>
{:matchers
[#'coconut.alpha/is-not
#'coconut.alpha/contains-value
#'coconut.alpha/contains
#'coconut.alpha/is
#'coconut.alpha/satisfies
#'coconut.alpha/or
#'coconut.alpha/throws
#'coconut.alpha/matches
#'coconut.alpha/and],
:api
[#'coconut.alpha/after-each
#'coconut.alpha/current-environment
#'coconut.alpha/after-all
#'coconut.alpha/let
#'coconut.alpha/done
#'coconut.alpha/around-each
#'coconut.alpha/deftest
#'coconut.alpha/before-each
#'coconut.alpha/it
#'coconut.alpha/before-all
#'coconut.alpha/deftest*
#'coconut.alpha/for
#'coconut.alpha/it*
#'coconut.alpha/describe
#'coconut.alpha/context
#'coconut.alpha/assert-that
#'coconut.alpha/environment],
:main
[#'coconut.alpha/run
#'coconut.alpha/explore]}
it
/deftest
vs. it*
/deftest*
?
Why In ClojureScript there is no way to retain bindings across asynchronous boundaries. Because of this, it
and deftest
won't work correctly. You can, but don't have to, use it
/deftest
for everything in Clojure and for synchronous tests in ClojureScript. For asynchronous tests in ClojureScript you'll need to use it*
/deftest*
and utilize the assert-that
/done
functions which are injected into the test function. Here's the relevant ticket for more information https://dev.clojure.org/jira/browse/CLJS-1634.