Friday 9 October 2015

Basic tests in a simple http-kit application

Tests are advisible because the application is about to undergo refactoring and it’s important to make sure that everything remains working as expected.

Before we jump in, update project.clj to use ring-mock

(defproject simple "0.0.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [ring/ring-devel "1.4.0"]
                 [ring.middleware.logger "0.5.0"]
                 [ring/ring-core "1.4.0"]
                 [ring-mock "0.1.5"]
                 [compojure "1.4.0"]
                 [org.clojure/java.jdbc "0.3.2"]
                 [postgresql "9.1-901.jdbc4"]
                 [javax.servlet/servlet-api "2.5"]
                 [http-kit "2.1.19"]]
  :plugins [[lein-environ "1.0.1"]])

Writing Tests

Unlike the main application source which lives in simple/src/simple, tests lives in the simple/tests/simple directory. I have a suspicion that the tests are likely to reveal some broken code, and when broken code is found, it will be patched.

Make sure you in the root of your project, and then create all the required directories…

mkdir -p tests/src/simple

Refactor the app

Before we write any tests, we need to refactor the app out of the (serve) function which lives in core.clj

;; ... all the rest of it
(def app
  (handler/site #'simple-routes))
(defn serve [& args]
  ; configure our log4j
    ;; now wrap the app in reload and logger
      (reload/wrap-reload app))
    {:port 8080}))

Testing Handlers

Here’s the tests we’ll use to test the handlers we have. This file should live under tests/simple/handlers_test.clj

(ns simple.handlers-test
  (:require [clojure.test :refer :all]
            [ring.mock.request :as mock]
            [simple.core :refer :all]))

(deftest test-ui-handler
  (testing "index GET"
    (let [response (app (mock/request :get "/"))]
      (is (= (:status response) 200))
      (is (= (:body response) "<p>get index</p>")))))

(deftest create-repmax
  (testing "index POST"
    (let [response (app (mock/request :post "/"))]
      (is (= (:status response) 200))
      (is (= (:body response) "<p>post index</p>")))))

(deftest display-for-user
  (testing "repmaxes-for-users GET"
    (let [response (app (mock/request :get "/sir-test-a-lot/"))]
      (is (= (:status response) 200))
      (is (= (:body response) "<p>Hello sir-test-a-lot</p>")))))

You can run the tests with lein tests but you will tire of this because of the high cost (read: looooooong wait) of starting up the JVM with leiningen. I find it is much better to load the tests up in a REPL and run them like that.

;; load up the refresh tool
(require '[ :refer [refresh]])
;; load up clojure test runner
(require '[clojure.test :refer [run-tests]])
;; load up our tests
(require 'simple.handlers-test)
;; now call our tests
(run-tests 'simple.handlers-test)
;; when required, reload the files

This works much nicer, it feels nice and fast to develop. This is how life should be, so avoid the restarting of the JVM at all costs. After running the tests you will notice one test cases fails. The fail is in the display-for-user test and it is caused by the use of the (concat) function. In the browser everything looks correct, but the value that is being returned is a sequence, not a string…

;; this line in core.clj
  (GET "/:username/", [username], (concat "<p>Hello " username "</p>"))

Caused this error …

>> FAIL in (display-for-user) (handlers_test.clj:24)
expected: "<p>Hello sir-test-a-lot</p>" to be an instance of clojure.lang.LazySeq
was: "<p>Hello sir-test-a-lot</p>" is an instance of java.lang.String

Changing core.clj to this…

;; this line in core.clj
  (GET "/:username/", [username], (clojure.string/join ["<p>Hello " username "</p>"]))

Reload the source file and run the tests again and everything passes…

user=> (refresh)
user=> (run-tests 'simple.handlers-test)
Testing simple.handlers-test
Ran 3 tests containing 6 assertions.
0 failures, 0 errors.
{:error 0 :fail 0 :pass 6 :test 3 :type :summary}


Now that tests are place, we’ll start making the handlers do something a little more useful than returning bland strings - returning JSON responses.

  1. Build the simplest possible http-kit app
  2. App running via lein run and live reloading of source
  3. Logging requests to stdout
  4. Routes with and without parameters
  5. Postgres integration
  6. Writing Tests
  7. Configuring the application to return JSON Responses (coming soon)
  8. A Clojurescript UI (coming soon)
  9. Websockets (coming soon)
  10. Putting your application into production (coming soon)

The full source code can be found on github and I’ve tagged the code for this post as 6.0.1.