What is an assertion in testing?
An assertion is a statement in a test that checks a result against a condition and fails the test if the condition does not hold. Developers also call it an assert. Many assertions compare an actual value from the code with an expected value, e.g. an order total of $240.00, and fail when the two differ.
The International Software Testing Qualifications Board (ISTQB) defines an assertion as a Boolean expression that should be true if and only if the software runs correctly. In practice, an assertion usually checks one part of a result, so it can stay true for some wrong results too.
A unit test with no assertion checks no result. It passes whenever the code does not crash, and the lines it runs still count toward code coverage, which records what ran, not what was checked.
How does an assertion work?
An assertion is the last step of arrange-act-assert, after the test sets up inputs and calls the code. It runs in four steps:
- The assertion takes the actual value that the code returned, e.g. an updated order.
- The assertion checks a condition on that value, usually that it equals an expected value.
- If the condition is true, the test goes on.
- If the condition is false, the assertion raises an error, e.g. Python's
AssertionError, and the test runner marks the test failed.
The expected value comes from the test's test oracle, the source that states the right result.
Assertions differ in how tests write them and in what a failure does:
- Assertion method. A test calls a method with the expected and actual values, e.g.
assertEquals(expected, actual)in JUnit. - Assert statement. Tests in pytest use Python's own
assertstatement, which pytest rewrites in test modules so that a failure shows the compared values. - Matcher. Jest and Playwright tests pass the actual value to
expect()and check it with a named method, e.g.toHaveLength(2). In Playwright, a browser test framework, matchers on a locator, e.g.toHaveText(), retry until they pass or time out. A generic matcher, e.g.toHaveLength(), checks once. - Soft assertion. Playwright's soft assertions, written with
expect.soft(), record a failure and let the test go on. A hard assertion, the default, stops the test at its first failure. - Custom assertion. A team adds its own named check for a condition it tests often, e.g. a Playwright matcher added with
expect.extend().
What is an example of an assertion?
Here is an illustrative example. Acme Co. sells furniture online. A developer asks a coding agent to "Let customers edit their delivery address during checkout." The agent writes the change and this pytest test:
def test_change_address():
order = Order(id="A-1042", items=["sofa", "lamp"])
updated = change_address(order, "12 Elm Street")
assert updated is not None
The test passes. Its one assertion is true for any order the function returns. The address saved, but the cart emptied. No assertion checks the cart.
The request changes only the address, so the cart should keep its items. A reviewer replaces the last line with two assertions whose expected values come from the request. The runner prints the compared values on its own, so the message on the second assertion states only the rule:
assert updated.address == "12 Elm Street"
assert updated.items == ["sofa", "lamp"], "cart must keep its items"
Both assertions check one behavior, an address change, so they share one test. A check of an unrelated behavior would go in its own test, so each failure points to one cause. On the same code, the first assertion passes and the second fails. The failure output shows the message and the two values that differed:
> assert updated.items == ["sofa", "lamp"], "cart must keep its items"
E AssertionError: cart must keep its items
E assert [] == ['sofa', 'lamp']
The agent changes change_address to keep the order's items, and both assertions pass. This example is simplified. A real checkout would need more assertions, e.g. one for the order total.
What changes when a coding agent writes the code?
A coding agent often writes its assertions in the same session as the code, with the code's output in its context. It can copy that output into an expected value, or write a condition that almost any result meets, e.g. the is not None check in the Acme test. A 2026 study of 86,156 changes to test files by five coding agents found that 80.2% had weak or no explicit oracle signals. An oracle signal is a check of a result against an expected value, e.g. an assertion that compares two totals.
A failed assertion brings a second risk. An assertion error shows that the actual and expected values disagree, not which of them is wrong. An agent asked to make the tests pass can edit the expected value instead of the code, and the test turns green on the broken change. That edit is a form of test tampering.
A practical adjustment is to write or approve expected values from the request before the agent writes code, as in test-driven development. Then check that each added assertion can fail. Break the code on purpose, e.g. make change_address drop one item, and run the test again. In review, a loosened condition or an edited expected value needs a reason from the request.
What makes an assertion weak?
A weak assertion stays true for some wrong results, so the test can report a false pass, a green run on broken software. Five patterns leave a test with weak or missing assertions:
- No assertion. A test with no assertion has no condition that can turn false. Some AI-written tests take this form.
- A condition almost any result meets. A check that a value is not
Nonepasses for almost any value the function returns. - An expected value taken from the code. A tautological test builds its expected value with the code's own logic, so it cannot fail.
- A check on one part of the result. An assertion checks only what it names, e.g. the address and not the cart. Assertion coverage counts the code whose results an assertion checks.
- An expected value recorded from the output. A snapshot test saves the code's first output as the expected value, so a wrong first output becomes the expected value and keeps passing.
Mutation testing finds weak assertions and missing tests across a whole project. It injects small bugs into the code and reports each bug that no test caught.
How is a test assertion different from an assert statement in code?
A test assertion checks the software's result from outside the code, and a false one fails a test. An assert statement in production code checks a condition inside the running program that the programmer expects to hold, e.g. that an order total is never negative. A false one raises an error in the program itself.
In Python, tests and production code use the same assert statement. Python skips assert statements when it runs with the -O option, so production code should not use them to check user input. Test runs normally leave that option off. A test that calls code with its own assert statement also fails when that statement is false. By default, pytest does not rewrite the asserts in application code, so that failure shows no compared values.
FAQs
What is a soft assertion?
A soft assertion is an assertion that records a failure and lets the test keep running, instead of stopping it at that line. The test still ends as failed. Soft assertions suit a test with several separate checks, because one run then reports each check that failed.
What does an assertion error mean?
An assertion error means that an assertion found its condition false, in a test or in an assert statement inside the program. In a test, the actual value usually differed from the expected value. The error does not say whether the code or the expected value is wrong, so someone checks the expected value against the request.
What should an assertion message say?
An assertion message should state the rule that the check protects, e.g. that the cart must keep its items. The test runner usually prints the compared values already, so the message adds the reason for the check rather than a copy of those values.
What is a custom assertion?
A custom assertion is a check that a team writes once and reuses for a condition it tests often, e.g. a matcher for an order's items. Playwright lets a team add such matchers to its expect function, and each test then calls the check by its name.
How many assertions should a test have?
A test should have as many assertions as it needs to check one behavior. Several assertions on one result, e.g. the address and the cart, are fine. Checks of unrelated behaviors belong in separate tests, so a failure points to one cause.