Introduction
In our last lesson, you built a comprehensive test suite for your GET endpoints, learning how to verify success (200), "not found" (404), and "bad request" (400) scenarios by controlling Mongoose's behavior with mocks.
This lesson continues that vital work by focusing on the POST endpoint. This is where we test the creation of new resources. Unlike GET requests which only read data, POST requests change the state of our application. Therefore, our testing must cover two critical paths:
- Successful Creation: Does the API correctly create a new resource when given valid data and return a
201 Createdstatus? - Validation Failure: Does the API correctly reject invalid data with a
400 Bad Requeststatus before attempting to save to the database?
By the end of this lesson, you will have the patterns to confidently test any resource creation endpoint.
1. Testing Successful Creation (The "Happy Path")
The goal here is to simulate a client sending valid data to create a new product. Your test needs to confirm that the controller correctly calls the Mongoose create method and sends back the newly created document with the appropriate 201 status code.
The Test Flow:
- Setup: Define the payload (the new product data) and the expected result from the database. Mock the
Product.create()method to resolve with this expected result. - Action: Use Supertest to send a
POSTrequest to/api/productswith the payload. - Assertion: Verify that the status code is
201, the response body matches the created product, and that theProduct.createmock was called with the correct data.
Let's implement this.
// In tests/routes/productRoutes.test.js
describe('POST /api/products', () => {
it('should create a new product and return 201 status', async () => {
// 1. Setup
const newProductData = {
name: 'Wireless Headphones',
price: 150,
description: 'Noise-cancelling over-ear headphones'
};
const savedProduct = {
_id: '60d21b4667d0d8992e610c87', // Mocked ID from the database
...newProductData
};
Product.create.mockResolvedValue(savedProduct);
// 2. Action
const res = await request(server)
.post('/api/products')
.send(newProductData); // .send() is used to provide the request body
// 3. Assertion
expect(res.statusCode).toBe(201);
expect(res.body).toEqual(savedProduct);
expect(Product.create).toHaveBeenCalledWith(newProductData);
});
});
This test gives us high confidence. We know the right status is returned, the created object is sent back to the client, and our controller is correctly interacting with the model.

To see this pattern in action, watch the first part of this video. It demonstrates testing a successful POST request and asserting the 201 status.
Supertest express tutorial | Node REST API testing
Watch this segment from the "Supertest express tutorial" by Jan Goebel to see a live demonstration of testing a successful POST request.
Watch from 04:53 to 06:55. The video shows how to use .post(), .send() a payload, and then expect() the status code to be 201. This is the exact pattern we are implementing.
2. Testing Validation Failure (The "Sad Path")
A robust API must reject invalid data. In our case, a name is required to create a product. Your validation logic (likely in the controller or a middleware) should catch this and return a 400 Bad Request without ever trying to call the database.
The Test Flow:
- Setup: Define an invalid payload (e.g., missing the
namefield). - Action: Use Supertest to send a
POSTrequest with the invalid payload. - Assertion: Verify the status code is
400. Crucially, also verify thatProduct.create()was not called. This confirms your validation is working correctly and preventing unnecessary database calls.
Here's the implementation:
// Add this 'it' block inside your 'POST /api/products' describe block
it('should return 400 status if required fields are missing', async () => {
// 1. Setup: Invalid data (missing name)
const invalidData = { price: 99 };
// 2. Action
const res = await request(server)
.post('/api/products')
.send(invalidData);
// 3. Assertion
expect(res.statusCode).toBe(400);
// Optional but highly recommended: Assert the error message
expect(res.body.message).toBe('Name is required'); // Adjust to your actual error message
// Most important: Ensure the database was not touched
expect(Product.create).not.toHaveBeenCalled();
});
This second video clip demonstrates this exact scenario, showing how to test for a 400 status and check the error message in the response body.
Supertest express tutorial | Node REST API testing
Now, watch the second part of the same video, which covers testing the error case.
Watch from 07:21 to 08:31. This shows how to send an incomplete payload and assert that the server correctly responds with a 400 status and the expected error message.
For a more comprehensive approach, especially when you have multiple required fields, you can test various invalid payloads in a single test case. The article "Automated Testing with Jest and Supertest in Node.js" provides a clean, text-based example of both the success and failure tests side-by-side.
Automated Testing with Jest and Supertest in Node.js
This article provides excellent, concise code examples for testing a POST endpoint. Review the provided test suite for creating a user.
In the section "Complete test suites", look at the describe("POST /api/v1/users", ...) block. It contains two tests: one that asserts a 400 status when data is missing, and another that asserts a 201 status when data is provided. This is a perfect template for your own tests.
Conclusion
You have now mastered the essential patterns for testing a POST endpoint. You can verify both successful resource creation and a graceful rejection of invalid data. Your test suite is becoming a powerful safety net, ensuring your API behaves as expected.
Key Takeaways:
- Test for Success (201):
- Mock
Model.create()to return a document. - Use
.post('/path').send(validData). - Assert status is
201. - Assert the response body matches the created document.
- Mock
- Test for Validation Failure (400):
- Send an incomplete or invalid payload using
.send(invalidData). - Assert status is
400. - Assert
Model.create()was not called.
- Send an incomplete or invalid payload using
Next Up:
With GET (read) and POST (create) operations fully tested, the next logical steps are to test the endpoints that modify existing data. In the following lesson, we will write integration tests for your PUT (update) and DELETE (remove) endpoints, covering their unique success and failure scenarios.