100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Testing with clojure.test

Learn Clojure's built-in testing framework — deftest, is, are, fixtures, and how to organize and selectively run tests in a real project.

Practical ClojureBeginner8 min readJul 10, 2026
Analogies

The clojure.test Building Blocks

clojure.test is Clojure's built-in testing framework, requiring no external dependency for basic unit testing. deftest defines a named test function, is makes an assertion (most commonly wrapping an = comparison, but works with any truthy expression), and testing wraps a block of assertions with a descriptive string label that shows up in failure output, making it easy to pinpoint which part of a test failed.

🏏

Cricket analogy: deftest is like registering a specific net session on the training schedule, and each assertion inside it is one specific ball you bowl to check the technique holds up, with a testing block grouping related deliveries under one labeled segment of the session.

Writing Assertions with is and are

Beyond plain equality checks, is (thrown? ExceptionClass ...) asserts that evaluating an expression raises an exception of a given type, and is (thrown-with-msg? ExceptionClass #"regex" ...) additionally checks the exception's message against a pattern. When you need to check the same relationship across many inputs, the are macro runs one assertion template against multiple rows of tabular test data in a single, compact form.

🏏

Cricket analogy: Checking one delivery scored the expected runs is a single is assertion, checking that an illegal delivery is correctly flagged as a no-ball uses thrown?, and a tabular scorecard checking many deliveries against expected runs at once is what are provides in one compact block.

clojure
(ns myapp.core-test
  (:require [clojure.test :refer [deftest is testing use-fixtures]]
            [myapp.core :as core]))

(defn reset-db-fixture [f]
  (core/reset-db!)   ; setup before each test
  (f)                 ; run the test
  (core/reset-db!))   ; teardown after each test

(use-fixtures :each reset-db-fixture)

(deftest greet-test
  (testing "basic greeting"
    (is (= "Hello, Ada" (core/greet "ada"))))
  (testing "rejects blank names"
    (is (thrown? IllegalArgumentException (core/greet "")))))

(deftest ^:integration db-roundtrip-test
  (core/save! {:id 1 :name "test"})
  (is (= "test" (:name (core/find-by-id 1)))))

Fixtures: Setup, Teardown, and Scope

use-fixtures wires setup and teardown logic around your tests instead of repeating it inside every deftest. Passing :each runs the fixture function around every individual test in the namespace, which is the safer default for resetting mutable state. Passing :once runs it a single time around the whole namespace's test run, appropriate for expensive, read-only setup like starting a shared test server.

🏏

Cricket analogy: use-fixtures :each is like re-marking the crease and resetting the field before every single ball is bowled in practice, ensuring no leftover state from the previous delivery, while :once sets up the entire net session just one time before all the deliveries and tears it down after the last one.

use-fixtures :each wraps setup/teardown around every single test in the namespace, which is the safer default for isolating mutable state. use-fixtures :once wraps setup/teardown around the whole namespace's test run one time — appropriate for expensive, read-only setup like starting a shared test server.

Organizing and Running Tests

Test namespaces conventionally mirror the namespace under test with a -test suffix, so myapp.core is tested in myapp.core-test, letting tooling discover and pair them automatically. Metadata tags on a deftest, such as ^:integration or ^:slow, let you selectively include or exclude subsets of the suite — for example running only fast unit tests on every commit while reserving slower integration tests for a nightly CI job.

🏏

Cricket analogy: Naming a namespace myapp.core-test mirrors labeling a specific practice video 'core-batting-drill-review' so it's instantly findable in the archive, and tagging tests with metadata like integration is like flagging certain net sessions as 'full-XI simulation only' so the coaching staff can run just those on demand.

Tests that leak state — such as a def-level atom that isn't reset between runs, or one test relying on side effects from another test running first — will pass or fail depending on execution order, which clojure.test does not guarantee. Always reset shared mutable state in a fixture rather than relying on incidental ordering.

  • deftest defines a named test; is asserts an expression is truthy (most commonly wrapping an = comparison); testing adds a descriptive label to a group of assertions.
  • is (thrown? ExClass ...) and is (thrown-with-msg? ExClass #"regex" ...) assert that an exception of a given type (and optionally message) is thrown.
  • are runs the same assertion template against multiple rows of test data in one compact tabular form.
  • use-fixtures :each runs setup/teardown around every test; use-fixtures :once runs it a single time around the whole namespace.
  • Test namespaces conventionally end in -test (e.g. myapp.core-test) and mirror the namespace under test.
  • Metadata tags like ^:integration or ^:slow on a deftest let you selectively include or exclude groups of tests when running the suite.
  • Never rely on test execution order for correctness — reset shared mutable state explicitly in a fixture to keep tests isolated.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ClojureStudyNotes#TestingWithClojureTest#Testing#Clojure#Test#Building#StudyNotes#SkillVeris#ExamPrep