Clojure Testing Guide — clojure.test, TDD, and Property-Based Testing
In this tutorial, you will learn about Clojure Testing Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Clojure testing uses clojure.test with deftest for test functions, is for assertions, are for data-driven tests, and testing for nested descriptions -- while test.check provides property-based testing with generators and automatic shrinking.
Basic Tests
(require '[clojure.test :refer :all])
(deftest test-addition
(is (= 4 (+ 2 2)))
(is (= 0 (- 2 2))))
(deftest test-string
(is (clojure.string/starts-with? "hello" "he"))
(is (not (clojure.string/blank? "hello"))))
;; Run single test
(run-tests *ns*)
;; With fixtures
(use-fixtures :each (fn [f] (println "Before") (f) (println "After")))
Assertions
;; Basic assertions
(is (= 4 (+ 2 2)))
(is (true? (even? 4)))
(is (nil? (get {} :key)))
;; With expected value
(is (= 4 (+ 2 2)) "Addition works")
;; Data-driven with are
(deftest test-math
(are [x y expected] (= expected (+ x y))
1 2 3
0 0 0
-1 1 0))
;; Exception testing
(deftest test-exceptions
(is (thrown? ArithmeticException (/ 1 0)))
(is (thrown-with-msg? Exception #"divide" (/ 1 0))))
Organizing Tests
(ns my-app.core-test
(:require [clojure.test :refer :all]
[my-app.core :refer :all]))
(deftest core-tests
(testing "Addition"
(is (= 4 (add 2 2)))
(is (= 0 (add 1 -1))))
(testing "String utils"
(is (= "HELLO" (my-app.core/upcase "hello")))))
;; Leiningen runs all *-test namespaces
Property-Based Testing
(require '[clojure.test.check :as tc]
'[clojure.test.check.properties :as prop]
'[clojure.test.check.clojure-test :refer [defspec]])
;; Property: reverse twice is identity
(defspec reverse-reverse 1000
(prop/for-all [v (gen/vector gen/int)]
(= v (reverse (reverse v)))))
;; Custom generators
(defspec sorted-property 100
(prop/for-all [v (gen/vector gen/int)]
(let [sorted (sort v)]
(and (= (count v) (count sorted))
(apply <= sorted)))))
Common Mistakes
1. Using is instead of are for repetitive tests
are creates multiple test cases from a template. Use it to reduce duplication.
2. Not using fixtures for setup/teardown
Use (use-fixtures :each setup-fn) for database cleanup or resource management.
3. Testing implementation details
Test public API behavior, not internal implementation. Refactoring shouldn't break tests.
Practice Questions
1. How do you define a test?
(deftest test-name (is (= expected actual))). The test is a function that runs assertions.
2. How do you test for an exception?
(is (thrown? ExceptionType expression)) passes if expression throws the specified exception.
3. How do you run all tests?
(run-tests) in the REPL, or lein test from the command line.
FAQ
{{< faq question="What is the difference between is and are?" >}} is tests a single assertion. are creates multiple assertions from a template and data table.
{{< /faq >}}
{{< faq question="How do I test asynchronous code?" >}}
Use clojure.core.async testing utilities or block on channels with (async/<!! ch).
{{< /faq >}}
{{< faq question="Can I use clojure.test with other test runners?" >}} Yes. Leiningen, Boot, and tools.deps all integrate with clojure.test. CIDER (Emacs) and Cursive (IntelliJ) support it. {{< /faq >}}
What's Next
Now learn about advanced Java interop.
| Topic | Description | Link |
|---|---|---|
| Java Interop | Advanced Java interop | {{< ref "14-java-interop" >}} |
| EDN | Extensible Data Notation | {{< ref "15-edn" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro