Friday, March 20, 2015

How to set up and run unit tests in ClojureScript

This tutorial covers initial setup of a development environment to run unit tests in ClojureScript project.

Install software

The installation process of required software depends from operation system on your computer. So please, refer to the documentation of your package manager or installer. I'm going to provide examples for Arch Linux, but I hope you will be easily tranlsate them to your operation system.

Leiningen

Leiningen is a build automation and dependency management tool for Clojure/ClojureScript projects. It can compile, build and test your Clojure and ClojureScript application. Leiningen automatically resolves library dependencies using Maven repositories.

The leiningen package is included in AUR repository in Arch Linux. So run yaourt leiningen and confirm installation of a package.

PhantomJS

PhantomJS is a scripted, headless WebKit-based browser used for automated interaction with web pages and Javascript. I'm going to setup development environment which will run unit tests inside PhantomJS. It's excellent tool for headless testing of web application.

In order to install phantomjs package in Arch Linux, run following command: sudo pacman -Syu phantomjs

Project structure

The Leiningen can generate initial skeleton of our application. Run following command: lein new cljs-unit-test. It creates following project structure:

.
└── cljs-unit-test
    ├── doc
    │   └── intro.md
    ├── LICENSE
    ├── project.clj
    ├── README.md
    ├── resources
    ├── src
    │   └── cljs_unit_test
    │       └── core.clj
    └── test
        └── cljs_unit_test
            └── core_test.clj

The current structure is Clojure-based only. And we change it to support all future cases to the following:

.
└── cljs-unit-test
    ├── doc
    │   └── intro.md
    ├── LICENSE
    ├── project.clj
    ├── README.md
    ├── resources
    │   └── private
    ├── src
    │   ├── clj
    │   │   └── cljs_unit_test
    │   └── cljs
    │       └── cljs_unit_test
    │           └── core.clj
    └── test
        └── cljs
            └── cljs_unit_test
                └── core_test.clj

As you see, we added separated folders src/clj, test/clj for Clojure and src/cljs, test/cljs for ClojureScript. Lets update project.clj file accordingly our project structure.


(defproject cljs-unit-test "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :source-paths ["src/clj" "src/cljs"]
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/clojurescript "0.0-3123"]])

We added :source-paths ["src/clj" "src/cljs"] key and [org.clojure/clojurescript "0.0-3123"] dependency to our project. Unfortunately, the leiningen cannot build ClojureScript by itself. So we need lein-cljsbuild.

lein-cljsbuild plugin

The lein-cljsbuild is a Leiningen plugin for automatically compilation of your ClojureScript code into Javascript. We need to modify our project.clj file to use lein-cljsbuild:


(defproject cljs-unit-test "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}

  :source-paths ["src/clj" "src/cljs"]

  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/clojurescript "0.0-3123"]]

  :plugins [[lein-cljsbuild "1.0.5"]]

  :hooks [leiningen.cljsbuild]

  :cljsbuild {
     :builds {
              :dev
              {:source-paths ["src/cljs"]
               :compiler {
                          :output-to "resources/private/js/main.js"
                          :optimizations :whitespace
                          :pretty-print true
                          }}
              :test
              {:source-paths ["src/cljs" "test/cljs"]
               :compiler {
                          :output-to "resources/private/js/unit-test.js"
                          :optimizations :whitespace
                          :pretty-print true
                          }}
              }}
)

We enabled lein-cljsbuild plugin and set up :dev and :test builds to produce separated Javascript files, because we don't want to include into our development and production Javascript files our tests. Also we don't enable aggresive optimization for debug purpose. So we are ready to add some application specific code to our project.

Lets modify src/cljs/cljs_unit_test/core.cljs file to following:


(ns cljs-unit-test.core)

(defn sum-two-numbers
  "Returns sum of two numbers"
  [x y]
  (+ x y))

(defn set-html! [el content]
    (set! (.-innerHTML el) content))

(defn ^:export init []
    (let [element (.getElementById js/document "main")
          content (str "2 + 2 = " (sum-two-numbers 2 2))]
        (set-html! element content)
        ))

It calculates sum of two numbers and displays it inside html element. The construction ^:export prevents tagged function to be removed during aggresive optimization of Javascript code.

Lets create html file for our demo application - resources/private/main.html:

<!doctype html>
<html lang="en">
<head>
    <title>Main</title>
</head>
<body>
    <h1 id="main"></h1>
    <script src="js/main.js"></script>
    <script>cljs_unit_test.core.init();</script>
</body>
</html>

clojurescript.test package

OK. We are ready to test our "super complex" application logic. But clojure.test works only for Clojure and we need ClojureScript specific unit test framework. It's clojurescript.test package. Lets add one more package to our project.clj file:


(defproject cljs-unit-test "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}

  :source-paths ["src/clj" "src/cljs"]

  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/clojurescript "0.0-3123"]
                 [com.cemerick/clojurescript.test "0.3.3"]]

  :plugins [[lein-cljsbuild "1.0.5"]
            [com.cemerick/clojurescript.test "0.3.3"]]

  :hooks [leiningen.cljsbuild]

  :cljsbuild {
     :builds {
              :dev
              {:source-paths ["src/cljs"]
               :compiler {
                          :output-to "resources/private/js/main.js"
                          :optimizations :whitespace
                          :pretty-print true
                          }}
              :test
              {:source-paths ["src/cljs" "test/cljs"]
               :compiler {
                          :output-to "resources/private/js/unit-test.js"
                          :optimizations :whitespace
                          :pretty-print true
                          }}
              }}
  )

We added [com.cemerick/clojurescript.test "0.3.3"] to our dependencies and plugins. Finally we can add our tests to test/cljs/cljs_unit_test/core_test.cljs:


(ns cljs-unit-test.core-test
    (:require-macros [cemerick.cljs.test
                      :refer (is deftest with-test run-tests testing test-var)])
    (:require [cemerick.cljs.test :as t]
              [cljs-unit-test.core :as core])
    )

(deftest test-sum-two-numbers
    (is (= (core/sum-two-numbers 2 2) 4))
    (is (= (core/sum-two-numbers 0 5) 5))
    )

Running unit tests

The last step is to configure our project to run unit tests inside PhantomJS. Modify project.clj file:


(defproject cljs-unit-test "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}

  :source-paths ["src/clj" "src/cljs"]

  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/clojurescript "0.0-3123"]
                 [com.cemerick/clojurescript.test "0.3.3"]]

  :plugins [[lein-cljsbuild "1.0.5"]
            [com.cemerick/clojurescript.test "0.3.3"]]

  :hooks [leiningen.cljsbuild]

  :cljsbuild {
     :test-commands {"unit" ["phantomjs" :runner
                             "resources/private/js/unit-test.js"]}
     :builds {
              :dev
              {:source-paths ["src/cljs"]
               :compiler {
                          :output-to "resources/private/js/main.js"
                          :optimizations :whitespace
                          :pretty-print true
                          }}
              :test
              {:source-paths ["src/cljs" "test/cljs"]
               :compiler {
                          :output-to "resources/private/js/unit-test.js"
                          :optimizations :whitespace
                          :pretty-print true
                          }}
              }}
  )

We specified :test-commands to run phantomjs executable with "resources/private/js/unit-test.js" file as argument.

Well. Finally, we can run our unit tests: lein cljsbuild test

And you will see following result:

Compiling ClojureScript.
Running ClojureScript test: unit

Testing cljs-unit-test.core-test 

Ran 1  tests containing 2 assertions.
Testing complete: 0 failures, 0 errors.

Build an application by running: lein cljsbuild once

And open resources/private/main.html in your favourite browser to manually verify how demo application works.

Tips: In order to speed up a little bit development, run following command in separated console: lein cljsbuild auto. It automatically builds your project after saving of files.

The final source code is available in my GitHub repository.

3 comments:

  1. I've been meaning to set up cljs tests in my current project. Thanks for the resource!

    ReplyDelete
  2. As a warning to anyone getting the following while trying to follow this tutorial.

    "ERROR: cemerick.cljs.test was not required."

    This may be due to a new bug introduced in cemerick.test 0.3.3. Try using version 0.3.2 and seeing if that fixes it for you. It worked for me!

    (Reference: https://github.com/cemerick/clojurescript.test/issues/91 )

    ReplyDelete