Doctests for Ocaml

You can test in the small, validate the code in your documentation, encourage testing, and get better documentation by leveraging MDX, a doctest-like tool for OCaml.

Zoological Division, Bureau of Animal Industry US Deptartment of Agriculture, Beltsville Maryland. 1935
Photo by The New York Public Library / Unsplash

Documentation-based tests for Ocaml, similar to what the doctest tool does for Python, and a similarly named tool for Haskell, is supported by OCaml's tooling. It's not especially obvious or well-documented, but it is well-supported. You do that using a tool called mdx. It was built to keep code up to date in the Real World OCaml book and is usable in your repository.

Get it to run on your mli files

There are a few different approaches to documentation in Ocaml. My assumption here is that you take the approach of creating mli files for all of your Modules and that you are documenting the modules in the mli file.

Start by dropping some code into a mli file.

(** Let's look at how good OCaml is with integers and strings:
    {@ocaml[
    # 1 + 2;;
    - : int = 2
    # "a" ^ "bc";;
    - : string = "ab"
    ]}
*)
This code comes right out of the MDX Readme.

Add the mdx dependency and stanzas to your dune-project

This assumes you have a dune based project with a directory that looks something like the following.

.
├── README.md
├── bin
│   ├── dune
│   └── main.ml
├── dune-project
├── lib
│   ├── config.ml
│   ├── dune
├── bar.opam
├── bar.opam.locked
├── test
│   ├── dune
│   └── bar.ml

Add (using mdx <version>) to your dune-project file. At the time of this writing, the version was 0.2. That may have changed by the time you read this. You also need to add mdx as a dependency in your package stanza.

(lang dune 3.6)

(name murios)
(version 0.1.0)

(generate_opam_files true)

(source
 (github foo/bar))

(authors "Calvin M. White <CalvinMWhite@jourrapide.com>")

(maintainers "snugly annoyed run")

(license UNLICENSED)
(using mdx 0.2)

(package
 (name murios)
 (synopsis "The murios data manipulation system")
 (description "The murios data manipulation system")
 (depends (ocaml (>= 5.0.0))
           mdx
           dune-build-info
           cmdliner
           odoc
           ocamlformat
           ocaml-lsp-server
           (eio (>= 0.8))
           eio_main utop
           sqlite3
           base
           core
           core_unix
           (dune (>= 3.7)))
 (tags
  (cmdline "universal takeover")))

; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
An example dune-project file with mdx enabled

Finally, you want to make sure that mdx is installed with opam.

# dune build
# opam install . --deps-only -y
# opam lock .

In the best case, you would have this as a target in Make or Make like automation.

Finally, update the dune file in the library, probably in lib/dune where you want to do the doctest style testing. It should look like the following.

(mdx
 (files :standard - *.mli)
 (libraries bar))

(library
 (name bar)
 (inline_tests)
 (preprocess
  (pps ppx_sexp_conv ppx_expect ppx_sexp_value))
 (libraries
  core
  eio.mock
  eio.unix
  eio_main
  core_unix
  ppx_expect
  ppx_sexp_value
  base
  sqlite3
  eio
  ppx_sexp_conv
  sexplib))

Notice the mdx stanza. Both the files part and the libraries part need to be in place or you will not successfully run the code embedded in your documentation . Ensure that mdx runs on the mli files and include the library you are trying to test in that stanza. In this example, that library is bar. Notice that name is the same name in both the mdx stanza and the library stanza. If you don't you will get an Unbound Module warning that looks something like the following.

File "lib/config.mli", line 1, characters 0-0:
diff --git a/_build/default/lib/msqlite.mli b/_build/default/lib/.mdx/config.mli.corrected
index ddef9c6..6f66a20 100644
--- a/_build/default/lib/config.mli
+++ b/_build/default/lib/.mdx/config.mli.corrected
@@ -7,8 +7,12 @@
 
     {@ocaml[
       # open Bar.Config;;
+      Line 1, characters 6-20:
+      Error: Unbound module Bar
       # "a" ^ "bc";;
       - : string = "abc"
     ]}

It is worth spending some time reading over the Mdx Stanza documentation for a full reference of what you can put there.

Running MDX Tests

Once you have done all of that, mdx will be wired into your dune based build. When you run the dune test target, dune runtest, it will also run the documentation based tests you have created.

Using this with the Promotion feature of dune makes this an incredibly powerful way to build tests.