Skip to content

Organizing Specificationsλ︎

Data and function definition specifications are typically placed in a dedicated specification namespaces, e.g src/domain/project/specification.clj.

Add the data specifications (spec/def), custom predicate functions and function specifications (spec/fdef) to the specifications namespace.

Specifications for an architecture layer can be organised next to the namespaces managing the layer, e.g. database.

Migrate specifications to a library once they are applicable to multiple projects.

Instrumenting functionsλ︎

Add spec-test/instrument expressions to the specifications file, after the spec/fdef expressions.

Rather than create individual expressions, create a clojure.core/def to contain a collection of all the spec/fdef expressions. This list can then be used to instrument and unstrument all the spec/fdef specifications.

(def ^:private function-specifications
  [`card-game/deal-cards
   `card-game/winning-player])

Write simple helper functions to wrap the instrumenting of function specifications

(defn instrument-all-functions
  []
  (spec-test/instrument function-specifications))

(defn unstrument-all-functions
  []
  (spec-test/unstrument function-specifications))

Unit testingλ︎

Specifications can be incorporated into the existing unit tests, so it is sensible to keep them under the corresponding test directory files.

Generative testingλ︎

Using spec-test/check will generate 1000 data values for each expression, so by default these tests will take far longer that other tests.

Configuring generative tests to only generate a small number of values will make spec-test/check expressions return almost instantaneously. In this example, only 10 data values are generated

(spec-test/check `deal-cards
                   {:clojure.spec.test.check/opts {:num-tests 10}})

Generative testing with small generators can be run regularly during development without impacting fast feedback.

Testing with Clojure Spec