Skip to content

Instantly share code, notes, and snippets.

@joelittlejohn
Last active July 3, 2023 21:08
Show Gist options
  • Save joelittlejohn/2ecc1256e5d184d78f30fd6c4641099e to your computer and use it in GitHub Desktop.
Save joelittlejohn/2ecc1256e5d184d78f30fd6c4641099e to your computer and use it in GitHub Desktop.
Dynamically generate clojure.test deftests (and other tricks)
(ns dynamic.test
(:require [clojure.test :refer :all]))
;; This example shows how tests can be generated dynamically, by
;; creating new vars with the correct metadata.
(defn add-test
"Add a test to the given namespace. The body of the test is given as
the thunk test-fn. Useful for adding dynamically generated deftests."
[name ns test-fn & [metadata]]
(intern ns (with-meta (symbol name) (merge metadata {:test #(test-fn)})) (fn [])))
(dotimes [i 10]
(add-test (str "two-times-" i "-is-even")
'dynamic.test
#(is (even? (* 2 i)))))
;; clojure.test will use test-ns-hook if it is defined, allowing the
;; order of tests to be explicit. This implementation re-uses the
;; clojure.test/test-vars function, but sorts the vars first.
(defn test-ns-hook
"Run tests in a sorted order."
[]
(test-vars (->> (ns-interns 'dynamic.test) vals (sort-by str))))
;; The test runner can be customized by defining your own
;; versions of multimethods like clojure.test/report. This one prints
;; the test name before running (with some ASCII escapes to turn the
;; text green)
(defmethod clojure.test/report :begin-test-var
[m]
(println "\u001B[32mTesting" (-> m :var meta :name) "\u001B[0m"))
;; clojure.test allows 'fixtures' to be added to wrap behaviour around
;; tests (or suites of tests). Here we add a simple fixture to output
;; the time each test takes:
(defn with-timing
"Fixture fn to print the time taken to test (in ms) after a test
completes."
[test]
(time (test)))
;; then in your test namespace:
(use-fixtures :each with-timing)
;; Sometimes you want to customize test reporting, but keep the original
;; behaviour too. Here's a simple way to do that:
(let [original-fn (:pass (methods clojure.test/report))]
(defmethod clojure.test/report :pass [m]
(println "Yay!")
(original-fn m)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment