Skip to content

lambdaisland/kaocha-cljs

Repository files navigation

kaocha-cljs

GitHub Actions cljdoc badge Clojars Project

ClojureScript support for Kaocha

Features

Kaocha-cljs provides basic ClojureScript support for the Kaocha test runner. It can run tests in the browser or in Node.js. It does this under the hood using the built-in ClojureScript REPL implementations for the browser or for node.

This approach makes it fairly easy to set up, but also fairly limited and inflexible.

  • Your code has to be compatible with vanilla ClojureScript
  • Shadow-cljs is not supported
  • We can only run tests with :optimizations :none
  • You have little to no control over the compiler settings
  • We don't support other runtimes besides Node and browser
  • Each run opens a new tab/process, we can't reconnect to existing JS runtimes
  • The repl-env abstraction is a black box which provide very little diagnostics

To get around these limitations we created kaocha-cljs2, which is infinitely flexible, but significantly harder to set up. For simple projects and libraries kaocha-cljs v1 can still be a valid choice. If it no longer serves your needs, you can try your hand at kaocha-cljs2.

Kaocha-cljs requires Clojure and ClojureScript 1.10 or later.

Installation

To use the latest release, add the following to your deps.edn (Clojure CLI)

com.lambdaisland/kaocha-cljs {:mvn/version "1.8.163"}

or add the following to your project.clj (Leiningen)

[com.lambdaisland/kaocha-cljs "1.8.163"]

For Node.js support also install the ws npm package, you can add something like this to bin/kaocha to this for you.

#!/usr/bin/env sh

[ -d "node_modules/ws" ] || npm install ws
clojure -A:dev:test -M -m kaocha.runner "$@"

To configure your kaocha-cljs test suite:

;; tests.edn
#kaocha/v1
{:tests [{:id :unit-cljs
          :type :kaocha.type/cljs
          ;; :test-paths ["test"]
          ;; :cljs/timeout 10000                        ; 10 seconds, the default
          ;; :cljs/repl-env cljs.repl.node/repl-env     ; node is the default
          ;; :cljs/repl-env cljs.repl.browser/repl-env
          }]}

And run your tests

bin/kaocha unit-cljs

Configuration

  • :kaocha/source-paths (or :source-paths when using #kaocha/v1)
    The location of your ClojureScript source paths (vector)
  • :kaocha/test-paths (or :test-paths when using #kaocha/v1)
    The location of your ClojureScript test paths (vector)
  • :cljs/timeout
    Time in milliseconds before timing out. This timeout gets reset whenever we receive an event from the ClojureScript environment, like a cljs.test event, or something being written to stdout. Once there is no activity for :cljs/timeout seconds, the test fails. This also causes subsequent tests to be skipped, because we assume the ClojureScript runtime is no longer responsive.
  • :cljs/repl-env
    A function (var) name which takes ClojureScript Compiler options, and returns a REPL environment. Values you can use include
    • cljs.repl.node/repl-env
    • cljs.repl.browser/repl-env
    • figwheel.repl/repl-env
  • :cljs/compiler-options
    Additional compiler options, defaults to {}.
  • :cljs/precompile?
    Invoke cljs.build.api/build before launching the REPL. Certain REPL types like Figwheel REPL require an initial build before the REPL is able to connect. If this is the case you can set this to true. Defaults to false.

Configuration for npm dependency

When using kaocha-cljs with :npm-deps, you need to:

  • enable precompilation
  • make sure :main, :install-deps, :npm-deps are all set in the compiler options

For example, if we want to compile the left-pad npm library as an npm dependency, the tests.edn should look like:

#kaocha/v1
    {:bindings        {kaocha.type.cljs/*debug* true}
     :capture-output? false
     :tests           [{:id                    :unit-cljs
                        :type                  :kaocha.type/cljs
                        :cljs/precompile?      true
                        :cljs/compiler-options {:main         npm_deps.main
                                                :verbose      true
                                                :install-deps true
                                                :npm-deps     {:left-pad "1.1.3"}}
                        :source-paths          ["src"]
                        :test-paths            ["test"]}]}

Note: We use the compiler options twice (if :cljs/precompile? is true): once to do the precompilation, and another once to start a REPL to communicate with.

Known issues

  • The :test-paths do not get automatically added to the classpath (at least not in a way that makes the sources visible to ClojureScript), so you need to also have any :test-paths in your project.clj/deps.edn/build.boot.

    This is a discrepancy with regular Kaocha, where you only need to specify the test paths once.

  • On Linux the cljs.repl.browser/repl-env requires the browser process to already be started before running Kaocha (see: https://clojure.atlassian.net/browse/CLJ-2493).

    To support running browser tests on CircleCI add an early config step like:

    - run:
        command: /usr/bin/google-chrome-stable --no-first-run
        background: true
    
  • You can not pass the :advanced optimization setting to the to the clojurescript compiler options, which is very important to run tests against a real build. If this feature is important you should consider using kaocha-cljs2 instead.

Common Errors

  • "Kaocha ClojureScript client failed connecting back."

This is the most common problem you'll encounter. Unfortunately it's a symptom that can have many underlying causes. What it means is this: Kaocha-cljs has created a ClojureScript repl-env, and asked it to evaluate the code which loads our websocket client. At this point Kaocha has to wait until that client is loaded, and has connected back to Kaocha, so we know we're in business.

For some reason this didn't happen in time, and so we time out and provide this error. What this really means is that the repl-env misbehaved. Maybe the JS runtime didn't start up properly (check your node process for instance), maybe the compiles CLJS caused an error (anything in the browser console)? Maybe it's a networking issue... We handed over control, and never got it back.

  • "Execution error (ReferenceError) at (<cljs repl>:1).\ndocument is not defined\n"

This usually means you're using libraries that expect a DOM (i.e., they expect to find js/document), but you are using them in an environment where there is no DOM, typically Node.

There are two ways to fix it:

  1. Instruct kaocha-cljs to use a browser REPL.
  2. Prepare a DOM for Node environment. For example, (js/require "global-jsdom") in a pre-test hook

Architecture

Kaocha's execution model

Most ClojureScript testing tools work by building a big blob of JavaScript that contains both the compiled tests and a test runner, and then handing that over to a JavaScript runtime.

Kaocha, however, enforces a specific execution model on all its test types.

[config] --(load)--> [test-plan] --(run)--> [result]

Starting from a test configuration (e.g., tests.edn) Kaocha will recursively load the tests, building up a hierarchical test plan. For instance clojure.test will have a test suite containing test namespaces containing test vars.

Based on the test plan Kaocha recursively invokes run on these "testables", producing a final result.

During these process various "hooks" are invoked (pre-test, post-test, pre-load, post-load), which can be implemented by plugins, and test events (begin-test-var, pass, fail, summary) are generated, which are handled by a reporter to provide real-time progress.

Kaocha's built-in features, plugins and reporters rely on this model of execution, so any test type must adhere to it. Note that all of this is on the Clojure side. Kaocha's own core, as well as plugins and reporters are all implemented in (JVM-based) Clojure, not in ClojureScript, so even in the case of ClojureScript tests the main coordination still happens from Clojure.

PREPL + Websocket

To make this work kaocha-cljs makes use of a ClojureScript PREPL (a programmable REPL). Given a certain repl environment function (e.g. browser/repl-env or node/repl-env) Kaocha will boot up a ClojureScript environment ready to evaluate code, and load a websocket client that connects back to Kaocha-cljs, so we have a channel to send data back from ClojureScript to Kaocha. It will then send code to the PREPL to load the test namespaces, and to invoke the tests.

Anything written on stderr or stdout will be forwarded to Clojure's out/err streams, and possibly captured by the output capturing plugin.

The test events produced by cljs.test (pass, fail, error) are sent back over the websocket, and ultimately handled by whichever Kaocha reporter you are using.

Events received from the PREPL and the websocket are all placed on a queue, which ultimately drives a state machine, which coordinates what needs to happen next, and gathers up the test results.

Debugging

If you're having issues, first try running with --no-capture-output. There may be relevant information that's being hidden.

To see all messages coming in over the PREPL and Websocket you can set kaocha.type.cljs/*debug* to true. You can do this directly from tests.edn.

#kaocha/v1
{:tests [,,,]
 :bindings {kaocha.type.cljs/*debug* true}}

This will also set the goog.log root logger, and the kaocha.cljs.websocket-client logger both to the DEBUG level. Have a look at glogi for more information about Google Closure's logging facilities.

When not using *debug* you can still set these log levels separately through :closure-defines.

#kaocha/v1
{:tests [{:type :kaocha.type/cljs
          :cljs/compiler-options {:closure-defines {kaocha.type.cljs/log-level "ALL"
                                                    kaocha.type.cljs/root-log-level "INFO"}}}]}
;; Log levels:
;; OFF SHOUT SEVERE WARNING INFO CONFIG FINE FINER FINEST ALL

Lambda Island Open Source

Thank you! kaocha-cljs is made possible thanks to our generous backers. Become a backer on OpenCollective so that we can continue to make kaocha-cljs better.

 

kaocha-cljs is part of a growing collection of quality Clojure libraries created and maintained by the fine folks at Gaiwan.

Pay it forward by becoming a backer on our OpenCollective, so that we continue to enjoy a thriving Clojure ecosystem.

You can find an overview of all our different projects at lambdaisland/open-source.

 

 

Contributing

We warmly welcome patches to kaocha-cljs. Please keep in mind the following:

  • adhere to the LambdaIsland Clojure Style Guide
  • write patches that solve a problem
  • start by stating the problem, then supply a minimal solution *
  • by contributing you agree to license your contributions as MPL 2.0
  • don't break the contract with downstream consumers **
  • don't break the tests

We would very much appreciate it if you also

  • update the CHANGELOG and README
  • add tests for new functionality

We recommend opening an issue first, before opening a pull request. That way we can make sure we agree what the problem is, and discuss how best to solve it. This is especially true if you add new dependencies, or significantly increase the API surface. In cases like these we need to decide if these changes are in line with the project's goals.

* This goes for features too, a feature needs to solve a problem. State the problem it solves first, only then move on to solving it.

** Projects that have a version that starts with 0. may still see breaking changes, although we also consider the level of community adoption. The more widespread a project is, the less likely we're willing to introduce breakage. See LambdaIsland-flavored Versioning for more info.

License

Copyright © 2018-2025 Arne Brasseur and Contributors

Licensed under the term of the Mozilla Public License 2.0, see LICENSE.

About

ClojureScript support for Kaocha

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 11