Liking cljdoc? Tell your friends :D

AsciiDoc test-doc-blocks Example

Test-doc-blocks will find Clojure source code blocks in your documents and generate tests for them.

The Basics

A test is generated for each Clojure code block. Any code block with with a (case insensitive) language of clj cljs cljc or starting with Clojure is recognized as a Clojure code block.

Assertions are automatically generated for:

  • REPL session style prompts :

    ;; test-doc-block will generate an assertion to verify (+ 1 2 3) evaluates to the expected 6
    user=> (+ 1 2 3)
    6
  • Editor style eval to comment, with some extras:

    ;; test-doc-blocks will generate an assertion to verify (+ 1 2 3 4) evaluates to the expected 10
    (+ 1 2 3 4)
    ;; => 10
    
    ;; it understands that Clojure and ClojureScript can evaluate differently
    \C
    ;; =clj=> \C
    ;; =cljs=> "C"
    
    ;; and verifies, when asked, to check what was written to stdout
    (println "hey there!")
    ;; stdout=> hey there!
    
    ;; multiple stdout lines can be verified like so (notice the use single of ;):
    (println "is this right?\nor not?")
    ;; =stdout=>
    ; is this right?
    ; or not?

    Sometimes we might care about evaluated result, stderr and stdout.

    ;; A snippit of Clojure where we check result, stderr and stdout
    (do
      (println "To out I go")
      (binding [*out* *err*] (println "To err is human"))
      (* 9 9))
    ;; => 81
    ;; =stderr=> To err is human
    ;; =stdout=> To out I go
    ;; And the same idea for ClojureScript
    (do
      (println "To out I go")
      (binding [*print-fn* *print-err-fn*] (println "To err is human"))
      (* 9 9))
    ;; => 81
    ;; =stderr=> To err is human
    ;; =stdout=> To out I go

Test-doc-blocks will plunk your code blocks into a test even when there are no REPL assertions found. It is sometimes just nice to know that your code snippit will run without exception.

(->> "here we are only checking that our code will run"
     reverse
     reverse
     (apply str))

Inline Options

You can, to some limited extent, communicate your intent to test-doc-blocks.

Test-doc-blocks will look for AsciiDoc comments that begin with :test-doc-blocks.

It currently understands four options:

  • :test-doc-blocks/apply - controls to what code blocks options are applied

  • :test-doc-blocks/skip - skips the next code block

  • :test-doc-blocks/reader-cond - wraps your code block in a reader conditional

  • :test-doc-blocks/test-ns - specifies the output test namespace

  • :test-doc-blocks/platform - specifies Clojure file type to generate for test ns

  • :test-doc-blocks/meta - attach metadata to generated tests

Applying Options - :apply

By default, new options are applied to the next Clojure code block only.

You can change this by including the :test-doc-blocks/apply option:

  • :next - default - applies new options to next Clojure code block only

  • :all-next - applies new options to all subsequent code blocks in the document until specifically overridden with new opts

Skipping Code Blocks - :skip

By default test-doc-blocks will create tests for all Clojure code blocks it finds.

Tell test-doc-blocks to skip the next Clojure code block via the following AsciiDoc comment:

//:test-doc-blocks/skip
[source,clojure]
----
;; no tests will be generated for this code Clojure code block

(something we don't want to test)
----

Wrap Test in a Reader Conditional - :reader-cond

A cljc library might want to explain ClojureScript vs Clojure usage without using reader conditionals in the code block.

To wrap the generated test for your code block in a reader conditional use the :test-doc-blocks/reader-conditional inline option.

This can be especially handy to show differences in (requires …​) for clj and cljs in separate code blocks. Here’s a somewhat contrived example.

Clojure specific code:

//#:test-doc-blocks {:reader-cond :clj}
[source,clojure]
----
;; This code block will be wrapped in a #?(:clj (do ...))
(require '[clojure.edn :refer [read-string]])
----

ClojureScript specific code:

//#:test-doc-blocks {:reader-cond :cljs}
[source,clojure]
----
;; This code block will be wrapped in a #?(:cljs (do ...))
(require '[cljs.reader :refer [read-string]])
----

Later in doc, cross-platform cljc code that relies on the above:

[source,clojure]
----
;; And our generic cljc code:
(read-string "[1 2 3]")
=> [1 2 3]
----

No special checking is done; but :reader-cond only makes sense for :cljc platform code blocks and when your code block contains no reader conditionals.

Specifying Test Namespace - :test-ns

If you don’t tell test-doc-blocks what namespace you want tests in, it will come up with one based on the document filename. For this file, test-doc-blocks, up to this point, has been generating tests to example-adoc-test.

If this does not work for you, you can override this default in via an AsciiDoc comment:

//{:test-doc-blocks/test-ns example-adoc-new-ns-test}
[source,clojure]
----
;; this code block will generate tests under example-adoc-new-ns-test

user=> (* 2 4)
8
----
Do what you like, but test runners usually look for tests namespaces ending in -test.

Changing the test-ns is useful for code blocks that need to be isolated.

//{:test-doc-blocks/test-ns example-adoc-new-ns.ns1-test}
[source,clojure]
----
;; this code block will generate tests under example-adoc-new-ns.ns1-test

(require '[clojure.string :as string])

(string/join ", " [1 2 3])
=> "1, 2, 3"
----

Specifying The Platform - :platform

By default, test-doc-blocks generates .cljc tests.

You can override this default on the command line via :platform and via inline option via :test-doc-blocks/platform. Valid values are:

  • :cljc - the default - generates .cljc test files

  • :clj - generates .clj test files

  • :cljs - generates .cljs test files

When specifying the platform, remember that:

  • For Clojure my-ns-file.clj will be picked over my-ns-file.cljc

  • For ClojureScript my-ns-file.cljs will be picked over my-ns-file.cljc

So if you are generating mixed platforms, you might want to specify the test-ns as well.

//#:test-doc-blocks{:platform :cljs :test-ns example-adoc-cljs-test}
[source,clojure]
----
;; this code block will generate a test under example-adoc-cljs-test ns to a .cljs file

(import '[goog.events EventType])
EventType.CLICK
;;=> "click"

(require '[goog.math :as math])
(math/clamp -1 0 5)
;;=> 0
----

Specifying Metadata - :meta

Test runners support including and excluding tests based on truthy metadata.

You can attach metadata to generated tests via the :test-doc-blocks/meta option.

A new :test-doc-blocks/meta will override any and all previous meta values.

We offer two syntaxes:

:test-doc-blocks-meta :my-kw

generates {:my-kw true} metadata.

:test-doc-blocks-meta {:my-kw1 my-value1 :my-kw2 my-value2}

the explicit option for those that need it

Example code blocks:

//#:test-doc-blocks{:meta :testing-meta123}
[source,clojure]
----
;; this code block will generate a test with metadata {:testing-meta123 true}

user=> (into [] {:a 1})
[[:a 1]]
----
//#:test-doc-blocks{:meta {:testing-meta123 "a-specific-value" :testing-meta789 :yip}}
[source,clojure]
----
;; this code block will generate a test with metadata:
;;  {:testing-meta123 "a-specific-value" :testing-meta789 :yip}

(reduce
   (fn [acc n]
     (str acc "!" n))
   ""
   ["oh" "my" "goodness"])
;; => "!oh!my!goodness"
----

Section Titles

Test-doc-blocks will try to give each test block some context by including its filename, section title and starting line number.

It recognizes that AsciiDoc recognizes CommonMark style single line headers.

## this type of md header

I think there is also support for 2 line headers but the rules might be a differ a bit from CommonMark. As 2 line CommonMark headers in a AsciiDoc file should be rare, we’ll not try to parse these variants in AsciiDoc docs for now:

And this level 1 type
=====================

And this level 2 type
---------------------

This code block should be include "Section Titles" as part of the context for its generated test.

```Clojure
(require '[clojure.string :as string])

(string/join "!" ["well" "how" "about" "that"])
;; => "well!how!about!that"
```

Support for CommonMark Code Block Syntax

Did you know AsciiDoc supports CommonMark syntax for section headings and code blocks?

Well it does! And test-doc-blocks recognizes this fact.

```Clojure
(require '[clojure.set :as set])

(set/map-invert {:a 1 :b 2})
;; => {1 :a, 2 :b}
```

Nuances

Inline requires and imports

It is common for REPL style code block examples to include inline requires and imports.

Test-doc-blocks will make an honest attempt to lift these inline requires up into the ns declaration of the generated test. This allows the generated tests to be run by ClojureScript which only supports inline requires in the REPL.

Test-doc-blocks should be able to handle common import and require formats. If we’ve missed one, let us know.

;; Stick the basics for requires, shorthand notation isn't supported

;; Some examples:
(require '[clojure.string :as string])
(require '[clojure.string])
(require 'clojure.string)
(require '[clojure.string :as string] '[clojure.set :as cset])

;; For cljc code examples it is fine for your requires and imports to contain, or be wrapped by, reader conditionals

;; Some examples of supported imports
#?@(:clj [(import 'java.util.List)
          (import '[java.util List Queue Set])]
    :cljs [(import 'goog.math.Long '[goog.math Vec2 Vec3])])

Test Run Order

In the general case, running tests in no specific or random order is a good thing. In the case of test-doc-blocks, this might not be what you want.

If your code blocks are self-contained examples, then test run order won’t be an issue for you. If your separate code blocks represent a larger flow, then order is important.

If we start in one code block…​

(defn fn-block1 [] (+ 1 2 3))

…​and continue in another:

(def var-block2 (+ 4 5 6))

(+ (fn-block1) var-block2)
;; => 21

…​and then maybe another:

(+ (fn-block1) var-block2 79)
;; => 100

... then run order is important to your generated tests.

Test-doc-blocks makes use of test-ns-hook in generated tests to specify the run order be the same as the doc blocks order in your documents.

Kaocha does not support test-ns-hook. It will by default randomize the order of tests for each test run. For Kaocha, randomization can be disabled from the command line via --no-randomize or in its tests.edn via :randomize? false.

When Libraries Override pr

The REPL makes use of pr to output what it has evaluated. The pr docstring states:

By default, pr and prn print in a way that objects can be read by the reader

Some libraries break this contract. For example, rewrite-clj overrides pr to display output for its nodes that is easily digestable by humans, but not at all digestable by Clojure.

If pr has been overriden for your library, you have choices for test-doc-blocks:

  1. Skip the block (see inline options)

  2. Avoid REPL assertions that effect the overriden pr

  3. Have your code blocks include call pr on affected evaluations and use =stdout⇒ to compare for expected output.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close