Search
Search documentation
Writing tests
Updated Oct 31, 2026
Mark a code block in any .md file as a runnable test by adding test to its
fence info string:
```python test
assert 1 + 1 == 2
```Untagged python blocks render normally and are not run, so you can freely
mix illustrative snippets and runnable assertions in the same article.
Test names
The test name comes from the nearest preceding heading. Two blocks under
the same heading are auto-suffixed #1, #2, and so on. A block with no
preceding heading uses the file's basename; YAML front matter never counts as a
heading. To override naming explicitly, see
Per-block options.
Assertions
Use plain assert: irondocs runs each block through pytest's assertion
rewriting, so a failing assertion explains itself just as it does in a test
module. pytest is injected too, so pytest.raises needs no import:
items = [1, 2, 3]
assert len(items) == 3
with pytest.raises(ZeroDivisionError):
1 / 0Imports
Any top-level import in a runnable block is hoisted and shared across every
test in the same .md file. Everything else a block defines stays local to it:
each block runs in a fresh namespace.
Hoisted imports run before any block, so a block's setup code (a sys.path
change, say) cannot prepare them; put such setup in conftest.py. Two blocks may
import the same name from the same module, but binding one name to two different
imports fails collection. An import used only by skip blocks, or by blocks an
only block leaves out, is guarded, so a module that doesn't exist yet cannot fail
the tests that run. A from __future__ import in any block applies to the whole file.
import jsonassert json.loads("[1, 2]") == [1, 2]Imports resolve as in any pytest module. Import your installed package, or put
its folder on the path with pytest's pythonpath option. A block is not part of
a package, so relative imports (from . import helper) fail; import a module
beside the doc by putting its folder on pythonpath.
Blocks are not pytest functions: they cannot request fixtures, and conftest.py
fixtures, autouse ones included, do not run for them. Do the setup in the block,
or in conftest.py at import time.
Async code
A block may await at the top level; irondocs then runs it in its own event
loop.
import asyncio
assert await asyncio.sleep(0, "done") == "done"Supported languages
Only python and py fences are picked up. pycon and other blocks render
but are not run.
Created with ❤ and Livemark