Testing a web application in Clojure is as important as testing in any other language. Clojure testing has been made simpler with its built in unit tests.
This blog explains about how to do unit testing(testing each module independently) of Clojure methods with some very basic syntax.
For Clojure testing we would be using a clojure.test testing namespace to load testing framework for placing the midje framework test cases.
(ns testing-namespace (:use clojure.test))
Firstly, add the following dependencies and plugins of Midje testing framework in your project.clj file:
:plugins [lein-midje "3.1.3"] :dependencies [midje "1.6.2"]
testing framework gives you choice from using any of the two test defining methods:
- defining test using with-test followed by method defination and test case.
(with-test (defn sum[s] (+ s 2)) (is (= 3 (sum 1)))
2. or defining your test cases in a seperate file using deftest macro(test function with no arguments) which improves the code quality.
Example: create two separate files-
(ns method.routes.core) (defn truthy? [value] (if (= value "true") true false)) (defn valid? [id email] (vali/rule (vali/has-value? id) [:id "Username required"]) (vali/rule (vali/is-email? email) [:email "Invalid Email Address" ]) (defn change-profile [old-password new-password confirm-password user] (if (password-match? old-password user) (if (confirmation-check? new-password confirm-password) (do ------------- (profile confirm-password)) (do (vali/rule false [:no-match "Password does not match"]) (profile old-password)))) (defn password-match[ old-password user] ;;------------------------) (defn confirmation-check[new-password confirm-password] ;;-------------------------)
methodTest.clj (Test file)
below namespace definition also requires importing the namespace of the file where the methods to be tested are defined followed by :refer :all for refering to every method in this namespace. Noir.util.test is required only to test those methods which uses clojure noir library validation checks.
(ns example.test.methodTest (:use clojure.test example.handler noir.util.test) (:require [method.routes.core :refer :all])) (deftest truthy-testing ;;for true test case: (is(truthy? "true"))) ;;for false test case: (is(truthy? "false"))))
(for leiningen clojure framework we are using lein test command in terminal within project name path to run the test.)
;;output for true test case i.e; no exception shown by passing expected value same as actual lein test example.test.methodTest Ran 1 tests containing 1 assertions. 0 failures, 0 errors. ;; output for false test case i.e;failure occured by passing expected value not same as actual FAIL in (truthy-testing) (methodTest.clj:9) expected: (truthy? "false")) actual: (not (truthy? "false")) Ran 1 tests containing 2 assertions. 1 failures, 0 errors. Tests failed.
Another example test case for testing validation method :
(deftest -validation-test (is (with-noir (auth/valid? "username" "email@example.com")) "message: valid") (is (with-noir (auth/valid? "" "firstname.lastname@example.org" )) "message: null value in a string") (is (with-noir (auth/valid? "username" "username")) "message: Invalid Email Address"))
output: first assertion shows true with no error message, rest 2 assertion gives described error message and failure test case
lein test example.test.methodTest lein test :only example.test.methodTest/validation-test FAIL in (validation-test) ( methdTest.clj:18) message: null value in a string expected: (with-noir (auth/valid? "" "email@example.com")) actual: false lein test :only example.test.methodTest/validation-test FAIL in (validation-test) (methodTest.clj:19) message: Invalid Email Address expected: (with-noir (auth/valid? "username" "username")) actual: false Ran 1 tests containing 3 assertions. 2 failures, 0 errors. Tests failed.
Example Test case for testing nested method by unit testing method
(deftest change-profile-test (is (= true (with-noir (password-match? "1234567890" "username")))"not found in database") (is (= true (with-noir (confirmation-check? "1234" "1234")))"invalid") (is (= true (with-noir (update-profile "1234567890" "1234" "1234" "username")))))
output: shows true result with no error message as the same data exists in database for “username”
lein test example.test.methodTest Ran 1 tests containing 1 assertions. 0 failures, 0 errors
*(is) in above test cases checks a single instance of method and throws exception if fails.Multiple assertions can also be checked within a single method by just defining multiple arguments and not same method again and again using(are).
*lein test command runs all tests in all namespaces. One can test the test cases under a single namespace also using lein test ‘your.namespace’.