Positive and Negative Testing
Positive testing checks that a system does what it should when it is used as intended: valid data, allowed actions, steps in the expected order. Negative testing, as the ISTQB glossary defines it, tests a system in a way it was not intended to be used: missing or invalid data, an action in the wrong state, a user without permission. Many teams also include error conditions, such as a service that does not answer.
At checkout, entering the current discount code SAVE20 and seeing the discount is a positive test. Entering RAMADAN24, which ended yesterday, and checking that it is refused with a clear message while the total stays the same is a negative test.
Why it matters
Users mistype, leave fields empty and press Back at the wrong moment; other systems time out. A product that works only on the happy path may lose a user's input after one mistake, answer an invalid request with a server error, or charge a customer twice. These are defects in the code that handles the unexpected, and they reach production when nobody designed a test for them.
Developers usually try the happy path before handing a feature over; the rejection paths are less likely to have been tried, so negative tests uncover much of the hidden risk. Positive tests stay essential: they prove that the feature serves its purpose.
Positive testing and the happy path
A positive test uses valid data and an allowed sequence of actions, and expects success: correct output, correctly stored data and the right side effects, such as a confirmation email. As in the lesson on functional testing, the expected result comes from the test basis, not from whatever the system happens to do.
The happy path is the main scenario in which everything goes as planned: typical data, default choices, no mistakes. Some teams call it the main success scenario. A feature has one happy path but usually several positive tests, because there is more than one valid way to succeed.
- Happy path: a cart of 250.00 SAR and the code
SAVE20(20% off) give a discount of 50.00 SAR and a total of 200.00 SAR. - Valid variations:
save20in lower case works too, because codes are not case-sensitive; removing the code returns the total to 250.00 SAR.
Negative testing: invalid input and unintended use
A negative test deliberately gives the system something it should not accept and expects a controlled rejection. The test passes when the system refuses exactly as specified. It fails when the system accepts the input, crashes, or refuses in a harmful way, such as clearing the whole form.
The test basis defines what is invalid; you will meet the same idea as invalid equivalence partitions in the test design module. Look for negative tests in these categories:
- Missing data: an empty required field, an API request without a body.
- Wrong format or type: letters in an amount, an email without
@, the date 31/02/2026. - Out of range: a quantity of 0 or -1.
- Wrong state or order:
RAMADAN24after its end date, paying for an order that is already paid. - No permission: opening another customer's order, only on systems you own or have written permission to test.
- A failed dependency: the payment provider times out.
Positive test
Valid input, intended use
- Question it answers:
- Does it work when used as intended?
- Typical input:
- Valid data, allowed actions, the expected order
- Example:
SAVE20on a cart of 250.00 SAR- Expected result:
- Discount 50.00, total 200.00, code shown as applied
- The test passes when:
- The action succeeds as specifiedPass
- A defect shows as:
- A wrong result, or valid input rejectedFail
Negative test
Invalid input or unintended use
- Question it answers:
- Does it refuse safely when used wrongly?
- Typical input:
- Missing data, wrong format, out of range, wrong state, no permission, a failed dependency
- Example:
RAMADAN24, which ended yesterday- Expected result:
- “This code has expired”, total still 250.00, no internal details shown
- The test passes when:
- The action is refused as specifiedPass
- A defect shows as:
- Invalid input accepted, a
500error, a vague message or changed dataFail
Both are tests that should pass: “negative” describes the input and the intent, not the outcome.
Try it: Negative Test Generator Try it: Validation Test Generator
Expected error handling: what a correct rejection looks like
"An error is shown" is not an expected result, because almost any behaviour passes it. Write down the parts of a correct rejection:
At API level, the request below sends the expired code. The test checks the status and the body, and a second request, GET /api/cart, confirms that the total is still 250.00 SAR.
- A specific message, in text next to the field, that says what to fix. Specific is not revealing: a login form says that the email or password is incorrect, not which one.
- No other change: no record created, no money moved, no email sent, and the user's input kept.
- No internal details: no stack trace or SQL error. The OWASP Error Handling Cheat Sheet recommends a generic message for the user and the details in the server log.
- The status code the API contract defines. RFC 9110 uses the 4xx class for errors in the client's request and 5xx for server errors, so invalid data gets a 4xx such as
400or422, never a500. - Recovery: once the input is corrected, the positive path works without starting again.
POST /api/cart/discount HTTP/1.1
Host: shop.example.com
Content-Type: application/json
{"code": "RAMADAN24"}
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json
{"error": "code_expired", "message": "This discount code has expired."}Try it: API Status Code Simulator Try it: Mock Error Response Generator
Worked example: tests for a discount code
Requirement: "At checkout, a customer can apply one discount code. A code works only between its start and end dates and once per customer, and it is not case-sensitive." The cart holds 250.00 SAR of eligible items.
Start with the happy path, then use one invalid condition per negative test, so that a failure points to exactly one rule. In DC-03 to DC-06 the total must stay 250.00 SAR. Designing negative tests also exposes gaps in the requirement, as DC-07 shows.
- DC-01, positive (happy path):
SAVE20→ discount 50.00, total 200.00. - DC-02, positive:
save20→ the same result as DC-01. - DC-03, negative: Apply with the field empty → "Enter a discount code".
- DC-04, negative:
NOPE99, which does not exist → "This code is not valid". - DC-05, negative:
RAMADAN24, which ended yesterday → "This code has expired". - DC-06, negative:
SAVE20a second time by the same customer → "You have already used this code". - DC-07, open question:
WELCOME10whileSAVE20is applied. Does it replace the first code or is it refused? Ask the product owner before writing the expected result.
Common mistakes
- Testing only the happy path, because that is what the demo and the acceptance criteria show.
- Writing "an error is shown" as the expected result, without the exact message or what must stay unchanged.
- Putting several invalid values in one test: the first error hides the others.
- Marking a negative test as failed because an error appeared, when that error was the expected result.
- Checking only the screen: the message appears, but the order was still created.
- Testing invalid input only through the form while the API behind it accepts the same data. Send it to the API too, on a system you are allowed to test.
The QA perspective
Negative testing starts in backlog refinement. For every acceptance criterion, ask "and what if not?": missing input, the wrong state, no permission, the payment service down. User stories often describe only the happy path, so these questions reveal error behaviour nobody has specified yet. Agree the messages with the product owner and the status codes with the developers before coding starts.
Mark each test case as positive or negative so suites can be filtered. Run the happy path first: if it is broken, report it at once, because it blocks most other tests. A bug report for a failed negative test names the exact input, the expected rejection, the actual behaviour and any side effect. Severity follows the consequence: an unclear message is usually minor, while invalid data saved or money moved is critical.
Practice exercise
Design positive and negative tests for a transfer
A banking app has this requirement: "A signed-in customer can transfer money between two of their own accounts. The amount must be greater than 0, have at most 2 decimal places and not exceed the available balance or 20,000.00 SAR per day in total. Source and destination must differ. After a successful transfer both balances update and a reference number is shown."
Design the tests on paper, then compare your list with the Positive Test Generator and the Negative Test Generator.
- Write the happy path with exact data and the expected result, then two more positive tests.
- Write at least one negative test per category from this lesson, with one invalid condition each.
- For every negative test, write the expected error handling.
- List the questions the requirement leaves open.
Show the model answer
Positive: 500.00 SAR from Current (available 2,000.00) to Savings succeeds with a reference number; Current shows 1,500.00 and Savings is 500.00 higher. This is the happy path. Also: 125.50 SAR succeeds, and Savings to Current succeeds.
Negative: empty amount ("Enter an amount"); abc ("Enter a valid amount"); 0 and -100 ("The amount must be greater than 0"); 12.345 ("Use at most 2 decimal places"); 2,500.00 with 2,000.00 available ("Insufficient balance"); the same account on both sides ("Choose a different account"); 600.00 after 19,800.00 sent today ("Daily limit exceeded").
State, permission and dependency: the session expires before Confirm (no transfer); another customer's account number sent through the API in your test environment (refused with a 4xx, nothing moves); the core banking service does not respond (a clear "try again later" message, and money never leaves Current without reaching Savings).
Error handling for every negative test: both balances unchanged, no reference number, no history entry, the input kept, no technical details.
Questions: which time zone defines "per day"? Is the limit per customer or per account? What does the customer see if the connection drops after Confirm?
Frequently asked questions
If a negative test passes, did the system fail?
No. A test passes when the actual result matches the expected result. For a negative test the expected result is a correct rejection, so the test passes when the system refuses the input as specified. If the system accepts the expired code, the test fails and you have observed a failure, caused by a defect in the input validation.
How many negative tests should I write compared with positive ones?
There is no fixed ratio. There are usually more ways to use a feature wrongly than rightly, so negative tests tend to outnumber positive ones. Choose them by risk: anything that touches money, personal data or permissions first, then the mistakes real users make most often.
Is negative testing the same as testing edge cases?
No. Negative testing is about intent: checking that invalid input or unintended use is refused. An edge case is a value or condition at an extreme, such as the longest allowed name or an empty list, and it can be valid or invalid. A name one character over the limit is both an edge value and a negative test.
References
- ISTQB GlossaryThe standard definitions of testing terms — test case, regression testing, severity, priority, boundary value analysis and the rest.
- ISTQB Certified Tester Foundation Level (CTFL) v4.0The foundation syllabus of software testing: test levels and types, test design techniques, test management and tools.
- OWASP Cheat Sheet Series: Error HandlingError responses that help the caller without leaking stack traces, versions or internal paths.
- RFC 9110: HTTP SemanticsThe meaning of HTTP methods, header fields, status codes and content negotiation, independent of the wire version.
Practise with Mutqan tools
Generate positive (happy-path) test cases that confirm each requirement and valid input combination works.
Generate negative test cases for form fields and APIs: empty, null, wrong type, too long, injection, invalid format.
Generate validation test cases for form fields: required, format, length, range, pattern and cross-field rules.
Get a public endpoint that returns any HTTP status code, with optional delay and custom JSON body, to test client error handling.
Generate structured error responses (validation, auth, not found, rate limit, server error) in popular formats.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.