Introduction
In the previous lesson, we built tests for your POST endpoint, ensuring it correctly handles both successful resource creation (201 Created) and validation failures (400 Bad Request). You now have a solid foundation for testing endpoints that create data.
This lesson focuses on endpoints that modify existing data: PUT requests. You will learn how to write integration tests for your update endpoint, covering the two most critical scenarios:
- Successful Update: The API finds the resource and updates it, returning a
200 OKstatus. - Not-Found Error: The API correctly returns a
404 Not Foundstatus when a client tries to update a resource that doesn't exist.
Mastering these patterns is essential for ensuring the reliability of your API's update functionality under exam conditions.
1. Testing a Successful Update (200 OK)
The "happy path" for a PUT request involves a client providing a valid resource ID and a payload of new data. Your test must confirm that the Mongoose findByIdAndUpdate method is called correctly and that the API returns the updated document with a 200 OK status.
The Test Flow:
- Setup: Define the ID of the resource to be updated and the update payload. Mock
Model.findByIdAndUpdate()to resolve with the final, updated document. - Action: Use Supertest to send a
PUTrequest to your endpoint (e.g.,/api/products/:id) with the update payload. - Assertion: Verify that the status code is
200, the response body matches the updated document, and thatModel.findByIdAndUpdatewas called with the correct ID and data.
Here's the pattern in code:
// In tests/routes/productRoutes.test.js
describe('PUT /api/products/:id', () => {
it('should update the product and return 200 status', async () => {
// 1. Setup
const productId = '60d21b4667d0d8992e610c87';
const updateData = {
name: 'Premium Wireless Headphones',
price: 199
};
const updatedProduct = {
_id: productId,
...updateData,
description: 'Original description' // Assuming description was not updated
};
// Mock the Mongoose method to return the updated document
Product.findByIdAndUpdate.mockResolvedValue(updatedProduct);
// 2. Action
const res = await request(server)
.put(`/api/products/${productId}`)
.send(updateData);
// 3. Assertion
expect(res.statusCode).toBe(200);
expect(res.body).toEqual(updatedProduct);
// Verify the mock was called correctly
expect(Product.findByIdAndUpdate).toHaveBeenCalledWith(
productId,
updateData,
{ new: true } // Assuming your controller uses this option
);
});
});
This test confirms your entire update logic, from the route handler to the model interaction, is working as expected for successful cases.
2. Testing Not-Found Errors (404 Not Found)
A robust API must gracefully handle requests to update resources that don't exist. Your controller should check if findByIdAndUpdate returns null and, if so, respond with a 404.
The Test Flow:
- Setup: Define an ID that does not correspond to any document in the database. Mock
Model.findByIdAndUpdate()to resolve withnull, simulating that no document was found. - Action: Use Supertest to send a
PUTrequest to/api/products/:idusing the non-existent ID. - Assertion: Verify that the status code is
404. It is also good practice to assert that your model's update method was called, but the controller handled thenullresponse correctly.
Here's how to implement this critical "sad path" test:
// Add this 'it' block inside your 'PUT /api/products/:id' describe block
it('should return 404 status if product is not found', async () => {
// 1. Setup
const nonExistentId = '123456789012345678901234';
const updateData = { name: 'This will fail' };
// Mock the Mongoose method to return null
Product.findByIdAndUpdate.mockResolvedValue(null);
// 2. Action
const res = await request(server)
.put(`/api/products/${nonExistentId}`)
.send(updateData);
// 3. Assertion
expect(res.statusCode).toBe(404);
// Optional: check for a specific error message if your API provides one
expect(res.body.message).toBe('Product not found'); // Adjust to your API's response
});
A successful test run for these scenarios provides visual confirmation that your code is robust. The goal is to see green checkmarks next to both your "successful update" and "return 404 if not found" tests.

A Complete Implementation Pattern
For a complete, exam-ready implementation pattern, the following resource provides an excellent and concise test suite for a PUT endpoint. It covers not only the 200 and 404 cases we've discussed, but also 400 errors for invalid ObjectIDs and missing request bodies, which you should also be prepared to handle.
Automated Testing with Jest and Supertest in Node.js
Study this example closely. It contains the exact test patterns you need for your exam. Focus on the describe("PUT /api/v1/users/:id", ...) block.
Navigate to the section titled "Complete test suites" and find the describe block for PUT /api/v1/users/:id. Analyze the four test cases: invalid ObjectId (400), user not found (404), missing data (400), and successful update (200). This is a comprehensive template for your own PUT endpoint tests.
By replicating these patterns, you build a powerful safety net that validates your API's update logic against all primary success and failure conditions.
Conclusion
You have now learned the essential patterns to write effective integration tests for PUT endpoints. Your test suite can now verify that your API updates resources correctly and handles attempts to update non-existent resources gracefully.
Key Takeaways:
- Test for Success (200):
- Mock
Model.findByIdAndUpdate()to resolve with an updated document. - Use
.put('/path/:id').send(updateData). - Assert the status is
200and the response body matches the updated document.
- Mock
- Test for Not Found (404):
- Mock
Model.findByIdAndUpdate()to resolve withnull. - Use
.put('/path/:nonExistentId'). - Assert the status is
404.
- Mock
Next Up:
The final piece of our CRUD testing puzzle is the DELETE endpoint. In the next lesson, you will learn how to write tests that confirm resources are removed correctly and that your API handles requests to delete non-existent items.