Doc-tests

Doc-tests

To keep code examples in docstrings up to date, Modo🧯 can generate test files for mojo test from them. Doctests are enabled by tests in the modo.yaml or flag --tests. Doctests are enabled by default. Further, the default setup also contains a post-processing script that runs mojo test.

Alternatively to modo build, Modo🧯’s test command can be used to extract tests without building the Markdown docs:

modo test           # only extract doctests

Tested blocks

Code block attributes are used to identify code blocks to be tested. Any block that should be included in the tests needs a doctest identifier:

```mojo {doctest="mytest"}
var a = 0
```

Multiple code blocks with the same identifier are concatenated.

Hidden blocks

Individual blocks can be hidden with hide=true:

```mojo {doctest="mytest" hide=true}
# hidden code block
```

Global blocks

Further, for code examples that can’t be put into a test function, global=true can be used:

```mojo {doctest="mytest" global=true}
struct MyStruct:
    pass
```

Full example

Combining multiple code blocks using these attributes allows for flexible tests with imports, hidden setup, teardown and assertions. Here is a full example:

fn add(a: Int, b: Int) -> Int:
    """
    Function `add` sums up its arguments.

    ```mojo {doctest="add" global=true hide=true}
    from testing import assert_equal
    ```

    ```mojo {doctest="add"}
    var result = add(1, 2)
    ```
    
    ```mojo {doctest="add" hide=true}
    assert_equal(result, 3)
    ```
    """
    return a + b

This generates the following docs content:

Function add sums up its arguments.

var result = add(1, 2)

Further, Modo🧯 creates a test file with this content:

from testing import assert_equal

fn test_add() raises:
    var result = add(1, 2)
    assert_equal(result, 3)

Note that the two code blocks around the box form a doc-test themselves, to ensure that this guide is correct and up to date. Both blocks have the attributes {doctest="add" global=true}, which concatenates them into one test file.

Markdown files

A completely valid Modo🧯 use case is a site with not just API docs, but also other documentation. Thus, code examples in Markdown files that are not produced by Modo🧯 can also be processed for doctests.

For that sake, Modo🧯 can use an entire directory as input, instead of one or more JSON files. The input directory should be structured like the intended output, with API docs folders replaced by mojo doc JSON files. Here is an example for a Hugo site with a user guide and API docs for mypkg:

        • _index.md
        • installation.md
        • usage.md
      • _index.md
      • mypkg.json
  • With a directory as input, Modo🧯 does the following:

    • For each JSON file (.json), generate API docs, extract doctests, and write Markdown to the output folder and tests to the tests folder.
    • For each Markdown (.md) file, extract doctests, and write processed Markdown to the output folder and tests to the tests folder.
    • For any other files, copy them to the output folder.

    Note that this feature is not available with the mdBook format.

    Modo🧯 vs. mojo test

    Mojo🔥 can also test code examples directly, see the manual’s section on testing. So why would you want to use Modo🧯’s doc-testing feature?

    First and foremost, mojo test will only test code blocks in docstrings, while Modo🧯 also allows for doc-testing of general Markdown files. Further, Modo🧯 is probably a bit more flexible and offers more control than mojo test. E.g., multiple tests suites are possible per docstring, and code blocks can be hidden completely where mojo test produces empty code blocks.

    Note that mojo test does not recognize docstrings with attributes (```mojo {...}). This means that code blocks prepared for Modo🧯’s doc-testing are not tested by mojo test. But it also means that you can’t use mojo test if you want to add attributes for your SSG, e.g. for line numbers, line highlights, or other features.

    The doc-testing features of Modo🧯 and mojo test can be used in the same project without conflicts.