Testing with pytest
jest → pytest
Section titled “jest → pytest”If you know jest, pytest will feel familiar in structure but delightfully simpler in syntax. No .toBe(), no expect() wrappers — pytest uses plain Python assert statements and its magic rewrites them to show helpful diffs on failure.
Installing pytest
Section titled “Installing pytest”Run this in your terminal:
pip install pytest# or:uv add --dev pytest
# Run all testspytest
# Run with verbose outputpytest -v
# Run a specific filepytest tests/test_math.py
# Run a specific test by namepytest tests/test_math.py::test_addWriting your first test
Section titled “Writing your first test”// jest — TypeScriptimport { add, greet } from './math';
describe('math utils', () => { test('adds two numbers', () => { expect(add(2, 3)).toBe(5); });
test('greet returns a string', () => { expect(greet('Alice')).toBe('Hello, Alice!'); });
test('throws on negative input', () => { expect(() => add(-1, 0)).toThrow('negative'); });});# pytest — Python# File must start with test_ or end with _test.py# Functions must start with test_
from math_utils import add, greetimport pytest
def test_adds_two_numbers(): assert add(2, 3) == 5
def test_greet_returns_string(): assert greet("Alice") == "Hello, Alice!"
def test_raises_on_negative_input(): with pytest.raises(ValueError, match="negative"): add(-1, 0)Test discovery rules
Section titled “Test discovery rules”pytest finds tests automatically — no test runner config needed to get started:
| Rule | pytest | jest |
|---|---|---|
| File pattern | test_*.py or *_test.py | *.test.ts or *.spec.ts |
| Function pattern | test_* | test(...) or it(...) |
| Class pattern | Test* (no __init__) | describe(...) |
| Setup | setup_method / fixtures | beforeEach |
| Teardown | teardown_method / fixtures | afterEach |
Fixtures — pytest’s beforeEach
Section titled “Fixtures — pytest’s beforeEach”In jest you use beforeEach / afterEach. pytest uses fixtures: dependency-injected functions that set up and optionally tear down resources. They are more powerful than beforeEach because they compose and have controlled scopes.
// jest beforeEachlet db: MockDatabase;
beforeEach(() => { db = new MockDatabase(); db.seed([{ id: 1, name: 'Alice' }]);});
afterEach(() => { db.close();});
test('finds a user', () => { expect(db.find(1)).toEqual({ id: 1, name: 'Alice' });});# pytest fixture — equivalent to beforeEach/afterEachimport pytest
@pytest.fixturedef db(): database = MockDatabase() database.seed([{"id": 1, "name": "Alice"}]) yield database # test runs here database.close() # teardown after yield
def test_finds_a_user(db): # pytest injects the fixture assert db.find(1) == {"id": 1, "name": "Alice"}Parametrize — data-driven tests
Section titled “Parametrize — data-driven tests”// jest — test.each for data-driven teststest.each([ [1, 2, 3], [0, 0, 0], [-1, 1, 0],])('add(%i, %i) = %i', (a, b, expected) => { expect(add(a, b)).toBe(expected);});# pytest — @pytest.mark.parametrizeimport pytest
@pytest.mark.parametrize("a, b, expected", [ (1, 2, 3), (0, 0, 0), (-1, 1, 0),])def test_add(a, b, expected): assert add(a, b) == expectedTry it — assertions in action
Section titled “Try it — assertions in action”# pytest-style assertions work in plain Python toodef add(a: int, b: int) -> int: return a + b
def test_basic(): assert add(2, 3) == 5 assert add(0, 0) == 0
def test_string(): message = "Hello, World!" assert "World" in message assert message.startswith("Hello") assert len(message) == 13
# Run the tests manuallytest_basic()test_string()print("All assertions passed!")Loading Python runtime (first run only)…
Playground note: The snippet above shows Python’s plain
assertstatements. The full pytest runner (test discovery, fixtures, parametrize,--cov) requires a terminal withpytestinstalled. Runpytest -vin your project to see the real experience.