pytest
error handling
unit testing
Python
software development

How to use pytest to check that Error is NOT raised

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In pytest, checking that an error is not raised is usually done by simply running the code and asserting expected outcomes. You only need special context managers when you want explicit structure around optional exception behavior. Clear assertions after execution provide stronger guarantees than exception absence alone.

Core Sections

Basic pattern in pytest

If no exception should happen, call the function directly.

python
1def divide(a, b):
2    return a / b
3
4
5def test_divide_success():
6    result = divide(10, 2)
7    assert result == 5

If an exception occurs, test fails automatically.

Add explicit assertion context

Use assertions on result values, state changes, or side effects so test verifies useful behavior, not just non-crash behavior.

python
1def parse_int(text: str) -> int:
2    return int(text.strip())
3
4
5def test_parse_int_valid():
6    value = parse_int(" 42 ")
7    assert value == 42

Using nullcontext for parameterized cases

When some cases should raise and others should not, use a shared parameterized structure.

python
1import pytest
2from contextlib import nullcontext
3
4
5def convert(text):
6    return int(text)
7
8
9@pytest.mark.parametrize(
10    "text, ctx",
11    [
12        ("10", nullcontext()),
13        ("x", pytest.raises(ValueError)),
14    ],
15)
16def test_convert(text, ctx):
17    with ctx:
18        convert(text)

This keeps mixed expectations concise.

Avoid anti-patterns

Do not wrap code in blanket try/except and then assert True. That can hide meaningful failures.

Test async functions

For async code, use pytest async support and await directly.

python
1import pytest
2
3@pytest.mark.asyncio
4async def test_async_no_error():
5    await some_async_function()

Validation and production readiness

Include regression tests for edge inputs that previously raised errors. Also assert logs or output state where relevant, so tests guard behavior rather than simply “did not crash”.

Check state and side effects, not only exceptions

A no-error test is strongest when it verifies outcome and side effects. For file-writing code, confirm that output exists and content is correct.

python
1from pathlib import Path
2
3
4def write_report(path: Path, value: int) -> None:
5    path.write_text(f"value={value}
6", encoding="utf-8")
7
8
9def test_write_report_no_error(tmp_path):
10    out = tmp_path / "report.txt"
11    write_report(out, 7)
12
13    assert out.exists()
14    assert out.read_text(encoding="utf-8") == "value=7
15"

If the function raises unexpectedly, test fails automatically. If it does not raise but writes wrong content, assertions still fail, which is exactly what you want.

Parameterize mixed expectations cleanly

Use one table for both success and failure paths so behavior remains explicit.

python
1import pytest
2from contextlib import nullcontext
3
4
5def parse_port(text: str) -> int:
6    value = int(text)
7    if value <= 0:
8        raise ValueError("port must be positive")
9    return value
10
11
12@pytest.mark.parametrize(
13    "raw, expected, ctx",
14    [
15        ("8080", 8080, nullcontext()),
16        ("-1", None, pytest.raises(ValueError)),
17        ("abc", None, pytest.raises(ValueError)),
18    ],
19)
20def test_parse_port(raw, expected, ctx):
21    with ctx:
22        value = parse_port(raw)
23        assert value == expected

This pattern scales well as rules grow and keeps no-raise cases readable.

Production checklist and verification loop

A reliable implementation needs more than a working snippet. Add a small verification loop that runs in CI and after dependency upgrades. Start with golden examples that represent normal input, boundary input, and one malformed input. Then validate output values, output shape or schema, and failure messages. This catches silent behavior drift early.

Document assumptions directly in the code comments near the transformation or query logic. Teams often forget whether behavior is strict, permissive, or backward-compatibility focused. Clear assumptions reduce future refactor risk.

For performance-sensitive paths, capture a baseline metric and compare after every change. The metric can be latency, memory use, or throughput depending on workload. Keep benchmark inputs realistic so results are meaningful.

Finally, expose observability signals that tell you when this logic starts failing in production. Useful signals include error counts, validation failures, and rate of fallback paths. A short checklist, a few deterministic tests, and lightweight monitoring are usually enough to keep this solution stable as surrounding systems evolve.

Common Pitfalls

  • Writing tests that only check no exception but not correctness.
  • Catching all exceptions manually and masking failures.
  • Mixing pytest.raises usage in tests that should pass normally.
  • Ignoring async marker setup for awaited functions.
  • Treating brittle happy-path tests as full error-handling coverage.

Summary

  • In pytest, code that should not raise is usually just executed directly.
  • Add assertions on outcomes to make tests meaningful.
  • Use nullcontext when parameterizing mixed raise and no-raise cases.
  • Avoid broad exception swallowing in tests.
  • Cover edge inputs and async behavior explicitly.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.