Testing in Clojure with `clojure.test` and `test.check`

Build useful Clojure test suites with focused example tests, property-based checks, and clear boundaries between unit, integration, and generative testing.

Testing in Clojure works best when the suite reflects the shape of the code. Pure functions deserve small, direct example tests. Stateful or integration-heavy code needs fixtures and boundary control. test.check becomes valuable when there is a property worth exploring across many inputs, not just because generative testing sounds more advanced.

Start with the Simplest Useful Test

For ordinary application code, clojure.test is usually the default starting point. It is lightweight, readable, and fits well with REPL-driven development.

1(ns myproject.pricing-test
2  (:require [clojure.test :refer [deftest is testing]]
3            [myproject.pricing :as pricing]))
4
5(deftest discount-is-capped
6  (testing "discount never exceeds the configured maximum"
7    (is (= 30 (pricing/cap-discount 30 50)))
8    (is (= 15 (pricing/cap-discount 30 15)))))

The test is valuable because it states one behavior clearly. It does not need a large framework, and it does not pretend to test more than it actually does.

Keep Test Boundaries Honest

Different kinds of code need different tests.

  • pure functions: example tests are often enough
  • stateful components: use fixtures and explicit setup/teardown
  • I/O boundaries: test with fakes, stubs, or dedicated integration environments
  • invariants across many inputs: consider test.check

The mistake is not using “too little testing technology.” The mistake is blurring unit, integration, and generative tests until nobody knows what a failing test actually means.

    flowchart TD
	    CODE["Code under test"] --> DECIDE{"What kind of behavior?"}
	    DECIDE -->|Pure function| UNIT["Focused example tests"]
	    DECIDE -->|Stateful or I/O boundary| INTEG["Fixtures and integration tests"]
	    DECIDE -->|Rule over many inputs| PROP["Property-based tests"]

Use clojure.test for Clarity

clojure.test works well because it keeps assertions close to the behavior being described.

1(use-fixtures
2  :each
3  (fn [test-fn]
4    ;; setup
5    (test-fn)
6    ;; teardown
7    ))

Fixtures are useful when a test needs controlled mutable state or external resources. They are not a substitute for isolating dependencies in the design itself.

Use test.check for Properties, Not for Everything

Property-based testing shines when there is a law or invariant that should hold across a broad input space.

1(ns myproject.collections-test
2  (:require [clojure.test.check.clojure-test :refer [defspec]]
3            [clojure.test.check.generators :as gen]
4            [clojure.test.check.properties :as prop]))
5
6(defspec reverse-twice-returns-original 100
7  (prop/for-all [xs (gen/vector gen/int)]
8    (= xs (reverse (reverse xs)))))

That is a strong property because it expresses something deeper than one example.

Weak uses of test.check look like this:

  • generating random data for code that has no meaningful invariant
  • replacing clear example tests with opaque property tests
  • writing properties so broad that failures are hard to interpret

The goal is not to make testing more abstract. The goal is to discover bugs that example tests would probably miss.

A Good Testing Mix in Clojure

A practical mix for many codebases is:

  1. small example tests around pure functions
  2. a smaller number of integration tests around boundaries
  3. targeted property tests around algebraic or structural invariants

That gives breadth without turning the suite into a slow, confusing pile of overlapping checks.

Common Mistakes

  • testing implementation details instead of behavior
  • writing brittle integration tests for what should be pure functions
  • using random generation without a meaningful property
  • relying only on example tests for code with strong invariants
  • letting fixtures mutate shared global state across unrelated tests

The strongest test suites are readable enough that a failing test immediately tells the team what kind of contract broke.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026