com.aaronlahey/coconut

An asynchronous testing framework for Clojure and Clojurescript.


Keywords
asynchronous, asynchronous-programming, behavior-driven-development, clojure, clojurescript, hamcrest, hamcrest-matchers, testing, testing-framework, testing-tools, unit-testing
License
MIT

Documentation

Coconut

Build Status Clojars Project

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.

  1. 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>
  2. 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

  1. 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]}))
  2. 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]}

Why it/deftest vs. it*/deftest*?

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.