Login

Testing Clojurescript - A simple approach

Article Summary
This article is part of a series. We use the "book club" project to explore various programming languages and frameworks. Details of the book club's business and data requirements are detailed in a prior article, "Leveraging Ruby on Rails and ClojureScript.".

This article details authomated testing og our Clojurescript client. Please see the corresponding article ClojureScript - Single Page Application - A simple approach for details on business requirements and implementation details.

We employ a stack of Java libraries and tools. Source code and build files are located on GitHub, at the book-site-clojurescript repository.

In this article we'll detail automated testing of our Clojurescript client. I'm using a port of clojure.test. cemerick/clojurescript.test. I am using phantomjs as a test runner (container).

Managing the Project with Leiningen
We are using the Leiningen to manage the Clojurescript project. Settings for Leiningen are placed in project.clj (located at the root directory of your project).

We need to add a contributed library to support automated tests for Clojurescript. We are using cemerick /clojurescript.test, a port of clojure.test to ClojureScript.

We make the following changes to the project.clj file.

We add the test library to the list dependencies and plugins:

:dependencies [[org.clojure/clojure "1.5.1"] [org.clojure/clojurescript "0.0-1859"] 
                      [com.cemerick/clojurescript.test "0.2.1"]]
:plugins [[lein-cljsbuild "0.3.3"] [com.cemerick/clojurescript.test "0.2.1"]
                      [marginalia "0.7.1"] [lein-marginalia "0.7.1"]]

Adding a new test target for the compiler.
Next we instruct the Clojurescript compiler to build 2 seperate targets. The first target includes our unit tests. The second target is what we'll use when deploying on the server (and ultimately delivered to the web browser client).

:cljsbuild
  { :builds [ { :source-paths ["src/cljs" "test" ]
                :compiler {:output-to "target/cljs/wstestable.js"
                         :optimizations :whitespace :pretty-print true}}
              {:id "prod" :source-paths ["src/cljs"]
                :compiler {:output-to "target/cljs/books_cljs.js"
                         :optimizations :whitespace :pretty-print true}}]
     :test-commands { "phantom-ws" [ "target/cljs/wstestable.js"]}
  }

Compiling Multiple Targets
With the above configuration we can compile just "prod" (production) target with the following command line:

$dev> lein cljsbuild once prod

Or, if we want our code compiled each time we save changes to disk. we substitute "once" with "auto":

$dev> lein cljsbuild auto prod

If we want to build both targets, just remove "prod" from the command line. For example to compile both targets just once:

$dev> lein cljsbuild once

Writing the tests.
In general these tests are much closer to the definition of "unit tests". The functions we are testing a very small. For example, a test of addAuthor[]. Let's look at the addAuthor source code:

(defn addAuthor
  ([id first_name last_name ]  
    (swap! AuthorList conj (Author. id first_name last_name )))
  ([jsonObj] (swap! AuthorList conj (jsonToAuthor jsonObj)) )
)

The above function can be called 2 different ways:

  1. With 3 parameters, an id, author's first name. author's last name
  2. WIth 1 parameter, a JSON object

Thus we have at least 2 tests of the AddAuthor[] function.

(deftest add-authors
  (testing "add authors"
    (p/addAuthor 0 "Mark" "Twain")
    (p/addAuthor 1 "Sam" "Shepard")
    (let [ 
          cnt (count  (deref p/AuthorList))
    ]
      (is (= cnt 2)) 
    )
  )  
)

We test the 3 parameter version of the function. We add 2 authors and then verify the number of author's in the AuthorList vector.

(deftest add-author-with-json 
  (testing "adding an author with a json object"
    (let
      [ jsonObj "{\"id\": 4, \"first_name\":\"Mary\",\"last_name\": \"Wallace\"}"]  
      (p/addAuthor jsonObj)
      (is (= (count (deref p/AuthorList)) 3))
    )
  ) 
)

This time we test the 1 parameter version of the function. Again we verify the result by checking the number of authors in the AuthorList vector.

Assertions
The following assertion verifes that the AuthorList vector contains 3 authors:

(is (= (count (deref p/AuthorList)) 3))

We can include several assertions in a single test. For example, the following test contains 2 assertions.

(deftest get-author
  (testing "add authors"
    (p/addAuthor 2 "Ann" "Sanders")
    (let [
          cnt (count (deref p/AuthorList))
          ann (p/get-list-item-by-id p/AuthorList 2)
    ]
      (is (= cnt 3))
      (is (= (:first_name ann) "Ann"))
    )
  )
)

The first assertion verifies that the AuthorList vector contains 3 authors. The second assertion verifies that the first name of the third Author is "Ann". Note! A vector's index values start with 0. Thus if you get the Author at index value 2, you are getting the third Author.

Running the tests.
We using Leiningen from the command line to run our tests:

$dev> lein cljsbuild test

Here is what the output looks like:

Compiling ClojureScript.
Running all ClojureScript tests.

Testing book-review.books_test

Ran 5 tests containing 6 assertions.
0 failures, 0 errors.

Summary
Adding unit testing to this portion of the Clojurescript project as very helpful. I needed to do some basic re-factoring (like changes to function names). The ability to quickly verify your changes have not adversely impacted the application is a big plus.

Article references.
As mentioned at the top, this article is part of a series focused on the "book club" project.

For this release of the book club project:
Changes to the Clojurescript client discussed in ClojureScript - Single Page Application - A simple approach
Changes to the Server tier are discuses in REST-MVC using Java
Testing the Server Tier discussed in Testing the Java REST-MVC Server Tier
GitHub Repository for Server tier: book-site-jpa
GitHub Repository for Clojurescript Client: book-site-clojurescript

About the Author:
Lorin M Klugman - I'm an experienced developer. My main interest is in new technology. Please use our contact box here if you are interested in hiring me. Please no recruiters :)