A maximal port of clojure.test
to ClojureScript.
I want to be able to write portable tests to go along with my portable
Clojure[Script], and clojure.test
's model is Good Enough™ (it's better than
that, actually). Combine with something like
cljx or
lein-cljsbuild's crossovers to
make your ClojureScripting a whole lot more pleasant.
clojurescript.test is available in Maven Central. Add this :dependency
to
your Leiningen project.clj
:
[com.cemerick/clojurescript.test "0.0.4"]
Or, add this to your Maven project's pom.xml
:
<dependency>
<groupId>com.cemerick</groupId>
<artifactId>clojurescript.test</artifactId>
<version>0.0.4</version>
</dependency>
clojurescript.test provides roughly the same API as clojure.test
, thus making
writing portable tests possible.
(Note that clojurescript.test
doesn't take any
responsibility for any hosty or otherwise-unportable
things you do in your tests, e.g. js/...
or naming JVM types or Clojure- or
ClojureScript-only functions; either don't do that, or use something like cljx
to include both Clojure and ClojureScript code in the same file.)
Here's a simple ClojureScript namespace that uses clojurescript.test:
(ns cemerick.cljs.test.example
(:require-macros [cemerick.cljs.test :refer (is deftest with-test run-tests testing)])
(:require [cemerick.cljs.test :as t]))
(deftest somewhat-less-wat
(is (= "{}[]" (+ {} []))))
(deftest javascript-allows-div0
(is (= js/Infinity (/ 1 0) (/ (int 1) (int 0)))))
(with-test
(defn pennies->dollar-string
[pennies]
{:pre [(integer? pennies)]}
(str "$" (int (/ pennies 100)) "." (mod pennies 100)))
(testing "assertions are nice"
(is (thrown-with-msg? js/Error #"integer?" (pennies->dollar-string 564.2)))))
You can load this into a ClojureScript REPL, and run its tests using familiar functions:
=> (t/test-ns 'cemerick.cljs.test.example)
Testing cemerick.cljs.test.example
{:fail 0, :pass 3, :test 3, :error 0}
All of the test-definition macros (deftest
and with-test
, as well as the
set-test
utility) add to a global registry of available tests (necessary given
ClojureScript's lack of namespaces), so you can also define, redefine, and run
tests interactively:
=> (deftest dumb-test
(is (empty? (filter even? (range 20)))))
#<[object Object]>
nil
=> (t/test-ns 'cemerick.cljs.test.example)
Testing cemerick.cljs.test.example
FAIL in (dumb-test) (:0)
expected: (empty? (filter even? (range 20)))
actual: (not (empty? (0 2 4 6 8 10 12 14 16 18)))
{:fail 1, :pass 3, :test 4, :error 0}
Most people use lein-cljsbuild to automate their ClojureScript builds. It also provides a test runner, originally intended for use with e.g. phantomjs to run tests that use existing JavaScript test frameworks. However, you can easily use the same facility to run clojurescript.test tests.
This is the lein-cljsbuild configuration that this project uses to run its own
clojurescript.test tests (look in the project.clj
file for the full monty):
:plugins [[lein-cljsbuild "0.3.0"]]
:hooks [leiningen.cljsbuild]
:cljsbuild {:builds [{:source-paths ["src" "test"]
:compiler {:output-to "target/cljs/testable.js"
:optimizations :whitespace
:pretty-print true}}]
:test-commands {"unit-tests" ["runners/phantomjs.js" "target/cljs/testable.js"]}}
Everything here is fairly basic, except for the :test-commands
entries, which
describes the shell command that will be executed when lein-cljsbuild's test
phase is invoked (either via lein cljsbuild test
, or just lein test
because
its hook is registered). In this case, it's going to run the phantomjs.js
script (which shebangs to phantomjs
), which will load the output of our ClojureScript
compilation, run all of the tests found therein, report on them, and fail the
build if necessary. Note that clojurescript.test supports all of Google Closure's
compilation modes, including :advanced
.
Feel free to grab the runners/phantomjs.js
script for your own projects (or, even
better, figure out a way to easily package it with clojurescript.test itself,
so only one such script will need to be maintained, etc).
Wanted: runners for other JavaScript environments, e.g. Rhino, XUL, node, etc
- Bug: filenames and line numbers are not currently reported properly.
- docstrings bear little to no semblence to the library's actual operation
- Namespace test hooks must be defined using the
deftesthook
macro
*report-counters*
is now bound to an atom, not a ref*testing-vars*
now holds symbols naming the top-levels under test, not vars*test-out*
is replaced by*test-print-fn*
, which defaults tonil
, and is only bound tocljs.core/*print-fn*
if it is bound to a non-nil value.run-tests
is now a macro;run-tests*
does the same, but does not offer a no-arg arityuse-fixtures
is now a macro, and there is no underlying multimethod to extend as inclojure.test
.
- Stack traces from caught exceptions are obtained via
Error.stack
, which appears to only be supported in Chrome, FF, Safari, and IE 10+. The value ofError.stack
in Rhino (at least, the version specified for use by ClojureScript) is always an empty string; other JavaScript environments may be similar. - File and line numbers of reported exception failures may be missing in
JavaScript environments that do not support the
lineNumber
orfileName
properties ofError
.
*load-tests*
is now private, and will probably be removed. The use case for Clojure (which is rarely taken advantage of AFAICT) seems irrelevant for ClojureScript; if you do or don't want tests in production, you just change your cljsc/lein-cljsbuild configuration.file-position
was already deprecated and unused- Not applicable
get-possibly-unbound-var
function?
*stack-trace-depth*
Send a message to the ClojureScript
mailing list, or ping cemerick
on freenode irc or
twitter if you have questions
or would like to contribute patches.
Copyright © 2013 Chas Emerick and other contributors. Known contributors to clojure.test
(which was the initial raw ingredient for this project) are:
- Stuart Sierra
- Rich Hickey
- Stuart Halloway
- Phil Hagelberg
- Tassilo Horn
- Mike Hinchey
Distributed under the Eclipse Public License, the same as Clojure.