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.