Markdown
Usage examples
Any fenced code block with a recognized Python language tag (e.g., python,
py) will be collected and executed automatically, if
your pytest configuration allows that.
Standalone code blocks
Note
Note that name value has a test_ prefix.
Filename: README.md
```python name=test_basic_example
import math
result = math.pow(3, 2)
assert result == 9
```
Grouping multiple code blocks
It’s possible to split one logical test into multiple blocks.
They will be tested under the first name specified.
Note the <!-- continue: test_group_new_syntax --> directive.
Note
Note that continue directive of
the test_grouping_example_part_2
and test_grouping_example_part_3 refers to
the test_grouping_example.
Filename: README.md
```python name=test_grouping_example
x = 1
```
Some intervening text.
<!-- continue: test_grouping_example -->
```python name=test_grouping_example_part_2
y = x + 1 # Uses x from the first snippet
assert y == 2
```
Some intervening text.
<!-- continue: test_grouping_example -->
```python name=test_grouping_example_part_3
print(y) # Uses y from the previous snippet
```
The above mentioned three snippets will run as a single test.
Note
Note, that nameless code block can’t be served as a first block in a group, as there is no way to refer to it. Nameless code blocks can only be used as continuing blocks in a group.
Async
You can use top-level await in your code blocks. The code will be automatically wrapped in an async function.
Filename: README.md
```python name=test_async_example
import asyncio
result = await asyncio.sleep(0.1, result=42)
assert result == 42
```
Adding pytest markers to code blocks
It’s possible to add custom pytest markers to your code blocks. That allows
adding custom logic and mocking in your conftest.py.
In the example below, django_db marker is added to the code block.
Note
Note the pytestmark directive django_db marker.
Filename: README.md
<!-- pytestmark: django_db -->
```python name=test_django
from django.contrib.auth.models import User
user = User.objects.first()
```
Running pytest-style tests within code blocks
The pytestrun marker allows code blocks to be executed as standalone pytest
suites. Unlike standard code blocks that are simply executed with exec(),
blocks with the pytestrun marker support full pytest functionality including
test classes, fixtures, and setup/teardown within documentation snippets.
Note
Note the pytestmark directive pytestrun marker.
Filename: README.md
<!-- pytestmark: pytestrun -->
```python name=test_pytestrun_example
import pytest
class TestSystemInfo:
@pytest.fixture
def system_name(self):
return "Linux"
@pytest.fixture
def version_number(self):
return 5
def test_combined_info(self, system_name, version_number):
info = f"{system_name} v{version_number}"
assert info == "Linux v5"
def test_name_only(self, system_name):
assert system_name.isalpha()
```
Requesting pytest fixtures for code blocks
It’s possible to request existing or custom pytest fixtures for code blocks.
That allows adding custom logic and mocking in conftest.py.
In the example below, tmp_path fixture is requested for the code block.
Note
Note the pytestfixture directive tmp_path fixture.
Filename: README.md
<!-- pytestfixture: tmp_path -->
```python name=test_path
d = tmp_path / "sub"
d.mkdir() # Create the directory
assert d.is_dir() # Verify it was created and is a directory
```
Let’s consider a sample openai code to ask LLM to tell a joke.
In the example below, openai_mock fixture is requested for
the code block.
Note
Note the pytestfixture directive openai_mock fixture.
Filename: README.md
<!-- pytestfixture: openai_mock -->
```python name=test_tell_me_a_joke
from openai import OpenAI
client = OpenAI()
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "developer", "content": "You are a famous comedian."},
{"role": "user", "content": "Tell me a joke."},
],
)
assert isinstance(completion.choices[0].message.content, str)
```
Multiple pytestfixture directives are supported. Add one on each line.
Note
When combining pytestfixture and continue directives together,
request pytest-fixtures only in the first code-block, as they will
automatically become available in all continuing blocks.
Custom pytest-fixtures are supported as well. Just define them in
your conftest.py file.
Customisation/hooks
Tests can be extended and fine-tuned using pytest’s standard hook system.
Below is an example workflow:
Add custom pytest markers to the code blocks (
fakepy,aws,openai).Implement pytest hooks in
conftest.pyto react to those markers.
Add custom pytest markers
Add fakepy marker
The example code below will generate a PDF file with random text
using fake.py library. Note, that a fakepy marker is added to
the code block.
In the Implement pytest hooks section, you will see what can be done with the markers.
Note
Note the pytestmark directive fakepy marker.
Filename: README.md
<!-- pytestmark: fakepy -->
```python name=test_create_pdf_file
from fake import FAKER
FAKER.pdf_file()
```
Add aws marker
Sample boto3 code to create a bucket on AWS S3.
Note
Note the pytestmark directive aws marker.
Filename: README.md
<!-- pytestmark: aws -->
```python name=test_create_bucket
import boto3
s3 = boto3.client("s3", region_name="us-east-1")
s3.create_bucket(Bucket="my-bucket")
assert "my-bucket" in [b["Name"] for b in s3.list_buckets()["Buckets"]]
```
Implement pytest hooks
In the example below:
moto is used to mock AWS S3 service for all tests marked as
aws.openai_mockis used to mock OpenAI API for tests requiring that.FILE_REGISTRY.clean_up()is executed at the end of each test marked asfakepy.
Filename: conftest.py
import contextlib
import json
import os
from pathlib import Path
from types import SimpleNamespace
import pytest
import respx
from fake import FILE_REGISTRY
from moto import mock_aws
from pytest_codeblock.constants import CODEBLOCK_MARK
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2025-2026 Artur Barseghyan"
__license__ = "MIT"
__all__ = (
"http_request",
"http_request_factory",
"markdown_simple",
"markdown_with_pytest_mark",
"openai_mock",
"pytest_collection_modifyitems",
"pytest_runtest_setup",
"pytest_runtest_teardown",
)
pytest_plugins = ["pytester"]
# Modify test item during collection
def pytest_collection_modifyitems(
config: pytest.Config,
items: list[pytest.Item],
) -> None:
"""Modify collected test items after collection is done.
:param config: The pytest configuration object.
:param items: A list of collected test items.
"""
for item in items:
if item.get_closest_marker(CODEBLOCK_MARK):
# Add `documentation` marker to `pytest-codeblock` tests
item.add_marker(pytest.mark.documentation)
if item.get_closest_marker("aws"):
# Apply `mock_aws` to all tests marked as `aws`
item.obj = mock_aws(item.obj)
# Setup before test runs
def pytest_runtest_setup(item: pytest.Item) -> None:
"""Set up test environment before each test runs.
:param item: The test item that is about to run.
"""
# Teardown after the test ends
def pytest_runtest_teardown(item: pytest.Item, nextitem: pytest.Item) -> None:
"""Tear down test environment after each test ends.
:param item: The test item that just finished running.
:param nextitem: The next test item that will run (or None if this is
"""
if item.get_closest_marker("fakepy"):
FILE_REGISTRY.clean_up()
@pytest.fixture
def http_request_factory():
"""
Returns a function that creates a simple namespace object
with a 'GET' attribute set to the provided dictionary.
"""
def _factory(get_data: dict):
# Creates an object like: object(GET={'key': 'value'})
return SimpleNamespace(GET=get_data)
return _factory
@pytest.fixture
def http_request(http_request_factory):
test_data = {"param1": "value1", "signature": "mock-sig"}
return http_request_factory(test_data)
@pytest.fixture
def openai_mock():
# Setup
os.environ.setdefault("OPENAI_API_KEY", "test-key")
cassette_path = (
Path(__file__).parent
/ "examples"
/ "cassettes"
/ "openai_chat_completion.json"
)
with open(cassette_path) as f:
response_data = json.load(f)
mock = respx.mock()
mock.start()
mock.post("https://api.openai.com/v1/chat/completions").respond(
json=response_data,
)
yield mock
# Teardown
with contextlib.suppress(Exception):
mock.stop()
@pytest.fixture
def markdown_simple():
return """
```python name=test_example
x=1
assert x==1
```"""
@pytest.fixture
def markdown_with_pytest_mark():
return """
<!-- pytestmark: django_db -->
```python name=test_db
from django.db import models
```"""
@pytest.fixture
def pytester_subprocess(pytester):
"""
Wrapper that forces subprocess mode to avoid deprecation warning conflicts
when the plugin uses the old `path` argument signature.
"""
pytester.runpytest = pytester.runpytest_subprocess
return pytester