testbook-tests
Use the `@testbook` decorator to write conventional pytest unit tests against functions defined in Jupyter notebooks, without copy-pasting the function into a `.py` file. Covers `tb.ref()` (notebook object access) and `tb.inject()` (insert code into the kernel) for hermetic per-test setup. Use when unit-testing functions that live in a Jupyter notebook.
Install with skills.sh (any agent)
npx skills add testland/qa --skill testbook-teststestbook-tests
testbook is a "unit testing framework for testing code in Jupyter Notebooks" per the testbook docs (opens in new window). It treats .ipynb files like .py files for testing - the test runs separately from the notebook itself.
When to use
Step 1 - Install
pip install testbook pytestPer the testbook docs (opens in new window).
Step 2 - Basic decorator pattern
Given a notebook cell:
def func(a, b):
return a + bTest:
from testbook import testbook
@testbook('/path/to/notebook.ipynb', execute=True)
def test_func(tb):
func = tb.ref("func")
assert func(1, 2) == 3Per the testbook docs (opens in new window): the decorator accepts the notebook path + execute parameter controlling cell execution.
Step 3 - tb.ref() for notebook objects
@testbook('analysis.ipynb', execute=True)
def test_clean_data_drops_nulls(tb):
clean_data = tb.ref("clean_data")
df = tb.ref("pd").DataFrame({"a": [1, None, 3]})
result = clean_data(df)
assert len(result) == 2tb.ref() returns a proxy to the notebook-side object - calls run in the kernel.
Step 4 - tb.inject() for setup code
@testbook('model.ipynb', execute=True)
def test_predict_with_specific_input(tb):
tb.inject(
"""
import numpy as np
np.random.seed(42)
test_input = np.array([[1.0, 2.0, 3.0]])
"""
)
predict = tb.ref("predict")
result = predict(tb.ref("test_input"))
assert result.shape == (1,)tb.inject() runs arbitrary code in the kernel - useful for deterministic seeding, mocking globals, fixture setup.
Step 5 - Selective cell execution
# Execute only specific cells (by tag)
@testbook('notebook.ipynb', execute=['imports', 'data-load'])
def test_with_partial_execution(tb):
df = tb.ref("df")
assert len(df) > 0Avoids slow training cells when testing pure-function helpers.
Step 6 - Pytest fixture (shared kernel)
import pytest
from testbook import testbook
@pytest.fixture(scope="module")
def tb():
with testbook('/path/to/notebook.ipynb', execute=True) as tb:
yield tb
def test_func_a(tb):
assert tb.ref("func_a")(1) == 2
def test_func_b(tb):
assert tb.ref("func_b")("x") == "X"Per the testbook docs (opens in new window): shared kernel context across tests via pytest fixtures - much faster than re-executing the notebook per test.
Step 7 - Object patching
@testbook('api_client.ipynb', execute=True)
def test_handle_api_failure(tb):
with tb.patch('requests.get') as mock_get:
mock_get.return_value.status_code = 500
result = tb.ref("fetch_data")()
assert result == {"error": "API failed"}tb.patch() mirrors unittest.mock.patch but operates inside the notebook's kernel.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Re-execute notebook per test | Slow; minutes per CI run | Use module-scoped fixture (Step 6) |
Use tb.ref() to fetch large DataFrames into test process | Serialization overhead; "non-serializable value" errors | Operate on the proxy via tb.ref() calls; only fetch primitives |
Skip execute=True and assume cells already ran | Notebook variables may not exist | Always execute=True (or scoped fixture) |
| Mix testbook + nbval on same notebook | Conflicting kernel sessions | Use one tool per notebook (or separate workflow runs) |
Inject arbitrary state via tb.inject for prod code | Tests pass but real notebook fails | Inject only test-specific setup (seeds, fixtures) |
Limitations
References
Related skills
nbval-tests
Validate Jupyter notebooks via the `pytest --nbval` plugin - re-execute cells and compare outputs to stored results. Cover the strict path (output match required), `--nbval-lax` (failure-only), `--sanitize-with` for dynamic outputs, and per-cell controls (`#NBVAL_SKIP`, `#NBVAL_IGNORE_OUTPUT`, `#NBVAL_RAISES_EXCEPTION`). Use when a repo ships `.ipynb` files as tutorials, docs, or analyses that must keep producing the same outputs after a dependency upgrade or source change.
notebook-ci-pipeline-author
Wires the papermill-tests, nbval-tests, and testbook-tests skills into a single working GitHub Actions CI pipeline: parameterized execution (papermill) -> output regression (nbval) -> function unit tests (testbook) -> artifact upload (executed .ipynb + HTML report). Use when a team has notebook tests spread across the three tools but assembles the pipeline manually and needs a single authoritative workflow file with output stripping (nbstripout), pip caching, and structured failure reporting.
papermill-tests
Use Papermill to parameterize and execute notebooks in CI as regression tests - `papermill input.ipynb output.ipynb -p alpha 0.6` (CLI) or `pm.execute_notebook(...)` (Python API). Use when notebooks must run as parameterized regression jobs in CI.