Skip to content

DocTestTest Your Documentation

Extract PHP code blocks from markdown. Execute them. Verify the output. Automatically.

DocTest
Basics

Write It. Test It. Trust It. ​

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.

Learn more →

markdown
```php
echo 'Hello, World!';
```
<!-- doctest: Hello, World! -->
bash
vendor/bin/doctest

  README.md
    :3 ✔ echo 'Hello, World!';  [1/1]  0.02s

  ----------------------------------------
  Blocks: 1  Passed: 1
  Tested: 100.0% (1/1)  Duration: 0.05s
Assertions

Seven Ways to Assert ​

From 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.

See all assertions →

markdown
```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
```
Control

Control with Attributes ​

Skip blocks, expect exceptions, or check syntax only. Attributes on the code fence give you precise control over how each block is handled.

Explore attributes →

markdown
```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');
```
Flexible

Wildcards for Dynamic Output ​

When output contains timestamps, IDs, or other dynamic values, wildcards let you match the pattern without hardcoding the value.

See all wildcards →

markdown
```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}}} -->
State

Shared State with Groups ​

Group related code blocks to share variables across examples. Add setup and teardown blocks for database connections or other resources.

Learn about groups →

markdown
```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');
```
Shiki

Shiki Compatible ​

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.

See Shiki support → · shiki-hide-lines →

markdown
```php

<?phprequire 'vendor/autoload.php'; 
$greeting = 'Hello';           
$greeting = 'Hello, World!';   
echo $greeting;
```
<!-- doctest: Hello, World! -->
Bootstrap

Per-Block Environments ​

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".

Learn about bootstrap profiles →

markdown
```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 -->
Update

Keep Docs in Sync ​

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.

Learn about update mode →

bash
vendor/bin/doctest docs/ --update

  docs/api.md
    :12 ✎ updated  <!-- doctest: old → new -->

  ----------------------------------------
  Updated: 1 assertion in 1 file
CI/CD

CI-Ready from Day One ​

Add one line to your CI pipeline. DocTest returns proper exit codes for your CI dashboard.

Set up CI →

yaml
# .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

Released under the MIT License.