Positive tests prove the API works. Negative tests prove it fails gracefully. Edge case tests prove it handles the weird stuff. If you skip these, you are only testing the happy path — which is maybe 30% of real-world traffic.
A negative test is not just "send bad data and check it does not crash." A good negative test verifies three things:
package com.capstone.api.tests;
import com.capstone.api.base.BaseTest;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import io.qameta.allure.Severity;
import io.qameta.allure.SeverityLevel;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
@Epic("JSONPlaceholder API")
@Feature("Negative Tests")
public class PostNegativeTests extends BaseTest {
// ===== Non-existent Resource Tests =====
@Test(groups = "regression")
@Severity(SeverityLevel.CRITICAL)
public void testGetNonExistentPostReturns404() {
given().spec(requestSpec)
.when()
.get("/posts/99999")
.then()
.statusCode(404);
}
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testGetNonExistentUserReturns404() {
given().spec(requestSpec)
.when()
.get("/users/99999")
.then()
.statusCode(404);
}
// ===== Invalid ID Tests =====
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testGetPostWithIdZero() {
given().spec(requestSpec)
.when()
.get("/posts/0")
.then()
.statusCode(404);
}
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testGetPostWithNegativeId() {
given().spec(requestSpec)
.when()
.get("/posts/-1")
.then()
.statusCode(404);
}
@Test(groups = "regression")
@Severity(SeverityLevel.MINOR)
public void testGetPostWithStringId() {
given().spec(requestSpec)
.when()
.get("/posts/abc")
.then()
.statusCode(404);
}
// ===== Missing Fields Tests =====
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testCreatePostWithEmptyBody() {
given().spec(requestSpec)
.body("{}")
.when()
.post("/posts")
.then()
.statusCode(201) // JSONPlaceholder accepts empty bodies
.body("id", equalTo(101));
}
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testCreatePostWithMissingTitle() {
given().spec(requestSpec)
.body("{\"body\": \"no title here\", \"userId\": 1}")
.when()
.post("/posts")
.then()
.statusCode(201) // JSONPlaceholder does not validate required fields
.body("body", equalTo("no title here"))
.body("userId", equalTo(1));
}
// ===== Wrong Data Type Tests =====
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testCreatePostWithStringUserId() {
given().spec(requestSpec)
.body("{\"title\": \"test\", \"body\": \"test\", \"userId\": \"not-a-number\"}")
.when()
.post("/posts")
.then()
.statusCode(201) // Mock API accepts it
.body("userId", equalTo("not-a-number"));
}
// ===== Invalid Endpoint Tests =====
@Test(groups = "regression")
@Severity(SeverityLevel.MINOR)
public void testInvalidEndpointReturns404() {
given().spec(requestSpec)
.when()
.get("/nonexistent")
.then()
.statusCode(404);
}
// ===== Filter Edge Cases =====
@Test(groups = "regression")
@Severity(SeverityLevel.NORMAL)
public void testFilterByNonExistentUserId() {
given().spec(requestSpec)
.queryParam("userId", 99999)
.when()
.get("/posts")
.then()
.statusCode(200)
.body("size()", equalTo(0)); // Empty array, not 404
}
@Test(groups = "regression")
@Severity(SeverityLevel.MINOR)
public void testGetCommentsForNonExistentPost() {
given().spec(requestSpec)
.when()
.get("/posts/99999/comments")
.then()
.statusCode(200)
.body("size()", equalTo(0)); // Empty array
}
}JSONPlaceholder is a mock API. It accepts almost anything — empty bodies, wrong types, missing fields. A real API would return 400 for these. The value of writing these tests now is learning the pattern. When you test a real API, these same tests will catch real bugs.
Pay attention to the comments in the code. We document the expected behavior of the mock API. In a real project, some of these tests would have different expected status codes.
| Category | What to Test | Expected Result |
|---|---|---|
| Non-existent resource | GET /resource/99999 | 404 Not Found |
| Invalid ID format | GET /resource/abc, /resource/-1, /resource/0 | 400 or 404 |
| Missing required fields | POST with empty body or missing fields | 400 Bad Request |
| Wrong data types | String where integer expected, number where string expected | 400 Bad Request |
| Unauthorized access | Request without auth token or with expired token | 401 Unauthorized |
| Invalid endpoint | GET /nonexistent | 404 Not Found |
| Method not allowed | PATCH on endpoint that only supports GET | 405 Method Not Allowed |
| Empty filter results | Filter by value that matches nothing | 200 with empty array |
| Very long input | POST with 10000-character strings | 400 or 413 |
| Special characters | POST with <script>, SQL injection strings | 400 Bad Request |
In interviews, when they ask about negative testing, list these categories. Most candidates say "I test with invalid data." You say "I test 10 categories: non-existent resources, invalid ID formats, missing required fields, wrong data types..." That is the difference between 3 LPA and 10 LPA answers.
Q: What negative test scenarios do you cover in your API tests?
A: I cover 10 categories of negative tests. Non-existent resources — GET with ID 99999, expect 404. Invalid ID formats — zero, negative, string IDs. Missing required fields — POST with empty body or missing mandatory fields, expect 400. Wrong data types — string where integer is expected. Unauthorized access — missing or expired auth tokens, expect 401. Invalid endpoints — request to non-existent URLs. Method not allowed — wrong HTTP method on an endpoint. Empty filter results — query parameters that match nothing. Very long input — strings with 10000+ characters. Special characters — HTML tags, SQL injection attempts. Each category covers a different failure mode that could occur in production.
Key Point: Cover 10 negative test categories: non-existent resources, invalid IDs, missing fields, wrong types, unauthorized, invalid endpoints, wrong methods, empty filters, long input, and special characters.