DocTestTest Your Documentation
Extract PHP code blocks from markdown. Execute them. Verify the output. Automatically.
Extract PHP code blocks from markdown. Execute them. Verify the output. Automatically.
Add an assertion after any PHP code block in your markdown. DocTest extracts the code, runs it, and verifies the output matches.
Your rendered documentation stays clean — assertions live in HTML comments, invisible to readers.
```php
echo 'Hello, World!';
```
<!-- doctest: Hello, World! -->vendor/bin/doctest
README.md
:3 ✔ echo 'Hello, World!'; [1/1] 0.02s
----------------------------------------
Blocks: 1 Passed: 1
Tested: 100.0% (1/1) Duration: 0.05sFrom exact output matching to JSON comparison, DocTest gives you the right tool for every situation.
HTML comments keep assertions invisible to readers. Use // => for inline result checks.
```php
echo 'Hello';
```
<!-- doctest: Hello -->
```php
echo 'The quick brown fox';
```
<!-- doctest-contains: brown fox -->
```php
echo date('Y');
```
<!-- doctest-matches: /^\d{4}$/ -->
```php
echo json_encode(['status' => 'ok']);
```
<!-- doctest-json: {"status": "ok"} -->
```php
$sum = array_sum([1, 2, 3]);
```
<!-- doctest-expect: $sum === 6 -->
```php
$x = 42; // => 42
```Skip blocks, expect exceptions, or check syntax only. Attributes on the code fence give you precise control over how each block is handled.
```php ignore
// This block won't be executed
$config = require 'missing-file.php';
```
```php throws(InvalidArgumentException)
throw new InvalidArgumentException('Bad input');
```
```php no_run
// Syntax checked, not executed
$db->query('SELECT * FROM users');
```When output contains timestamps, IDs, or other dynamic values, wildcards let you match the pattern without hardcoding the value.
```php
echo 'Request took 42ms at ' . date('Y-m-d');
```
<!-- doctest: Request took {{int}}ms at {{date}} -->
```php
echo json_encode([
'id' => '550e8400-e29b-41d4-a716-446655440000',
'time' => '14:30:00',
'cost' => 19.99,
]);
```
<!-- doctest: {"id":"{{uuid}}","time":"{{time}}","cost":{{float}}} -->Group related code blocks to share variables across examples. Add setup and teardown blocks for database connections or other resources.
```php setup group="database"
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT
)');
```
```php group="database"
$pdo->exec("INSERT INTO users (name) VALUES ('Alice')");
$count = $pdo->query('SELECT COUNT(*) FROM users')
->fetchColumn();
echo $count;
```
<!-- doctest: 1 -->
```php teardown group="database"
$pdo->exec('DROP TABLE users');
```Write documentation with Shiki markers — diffs, highlights, and hidden boilerplate. DocTest strips the markers and executes the real code underneath.
Works with any Shiki-powered tool: VitePress, Astro, Nuxt Content, Slidev. Hide setup lines with // [!code hide] so readers see clean examples while tests run the full code.
```php
··· 2 hidden lines
$greeting = 'Hello';
$greeting = 'Hello, World!';
echo $greeting;
```
<!-- doctest: Hello, World! -->Different code blocks need different setups? Bootstrap profiles let each block load only what it needs. Create .doctest/laravel.php and .doctest/database.php, then tag blocks with bootstrap="laravel" or compose them with bootstrap="laravel,database".
```php bootstrap="laravel"
echo config('app.name');
```
<!-- doctest: Laravel -->
```php bootstrap="laravel,database"
$count = DB::table('users')->count();
echo $count;
```
<!-- doctest: 0 -->
```php
echo strtoupper('hello');
```
<!-- doctest: HELLO -->Code changed but forgot to update the docs? The --update flag automatically rewrites stale assertion values with actual output. Use <!-- doctest-output --> to mark display-only blocks that get refreshed but never asserted.
Look for the ✎ symbol — it means DocTest updated an assertion for you.
vendor/bin/doctest docs/ --update
docs/api.md
:12 ✎ updated <!-- doctest: old → new -->
----------------------------------------
Updated: 1 assertion in 1 fileAdd one line to your CI pipeline. DocTest returns proper exit codes for your CI dashboard.
# .github/workflows/docs.yml
name: Documentation Tests
on: [push, pull_request]
jobs:
doctest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- run: composer install
- run: vendor/bin/doctest