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 "" 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. ```python name=test_grouping_example_part_2 y = x + 1 # Uses x from the first snippet assert y == 2 ``` Some intervening text. ```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* ```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* ```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* ```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* ```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: 1. **Add custom pytest markers** to the code blocks ("fakepy", "aws", "openai"). 2. **Implement pytest hooks** in "conftest.py" to 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* ```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* ```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_mock" is used to mock OpenAI API for tests requiring that. * "FILE_REGISTRY.clean_up()" is executed at the end of each test marked as "fakepy". *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 " __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 """ ```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