Introduction
In our last lesson, you implemented a robust validation flow for our file upload endpoint, handling cases for valid files, invalid file types, and missing files. Your API now provides clear, specific feedback for each scenario.
This lesson bridges the gap between functionality and reliability. We will write an automated integration test for this endpoint using Jest and Supertest. You'll learn the most critical pattern for this task: using the .attach() method to simulate a file upload directly in your test code. This ensures that your file upload logic remains correct and prevents future code changes from breaking it—a non-negotiable skill for production-level development and a common requirement in certification exams.
1. Preparing for the File Upload Test
Automated tests need to be self-contained and repeatable. When testing a file upload, we can't rely on a random file existing on the machine where the tests are run. The standard practice is to include a "fixture" file—a dummy file that is part of your project—specifically for testing purposes.
Let's set this up now.
Action Steps:
- In your project's root directory, create a new folder named
tests. - Inside the
testsfolder, create another folder namedfixtures. - Find a small, simple image file (e.g., a
.pngor.jpg) on your computer. It can be anything. Rename it totest-image.pngand place it inside thetests/fixtures/directory. - Create a new text file inside
tests/fixtures/and name ittest-document.txt. Add some text to it.
Your project structure should now look something like this:
/project-root
├── node_modules/
├── routes/
│ └── uploadRoutes.js
├── tests/
│ ├── fixtures/
│ │ ├── test-image.png
│ │ └── test-document.txt
│ └── ... (your other test files)
├── .env
├── .gitignore
├── app.js
└── package.json
2. Testing File Uploads with .attach()
Supertest is designed to handle all kinds of HTTP requests, including the multipart/form-data type used for file uploads. The key to this is the .attach() method.
The syntax is straightforward: .attach('fieldName', 'path/to/your/file')
fieldName: This is a string that must match the field name that Multer is configured to look for. In yourupload.single('productImage')middleware, the field name is'productImage'.path/to/your/file: This is the path to your fixture file. To make this path reliable, we'll use Node.js's built-inpathmodule.
Let's look at a detailed example of how this is implemented.
API Testing with Supertest and Jest - Library - Grizzly Peak Software ...
The article 'API Testing with Supertest and Jest' from Grizzly Peak Software provides an excellent, production-grade example of how to test file uploads.
Please read the section titled 'Testing File Uploads.' Focus on the first test case, 'uploads an image.' Notice three key things: It imports the path module. It constructs the file path using path.join(__dirname, 'fixtures', 'test-image.png'). __dirname is a Node.js variable that gives you the path of the current directory, ensuring your tests can be run from anywhere. It uses .attach('file', ...) to add the file to the request.
The resource How to Test Your Express APIs with Supertest also demonstrates this same pattern with the .attach() method in its "File Upload" section, confirming this is the standard approach.
3. Your Turn: Write the Upload Test
Now, it's time to apply this pattern. You will create a new test file and write a test that verifies the successful upload of a valid image.
Action Steps:
- In your
tests/directory, create a new file namedupload.test.js. - Add the following code to
upload.test.js. This test simulates sending a POST request with a valid image file attached.
// tests/upload.test.js
const request = require('supertest');
const path = require('path');
const app = require('../app'); // Adjust this path if your app entry point is different
describe('File Upload API', () => {
// Test for successful file upload
it('should upload a valid image file and return 200', async () => {
const filePath = path.join(__dirname, 'fixtures', 'test-image.png');
const res = await request(app)
.post('/api/upload') // Ensure this matches your upload route
.attach('productImage', filePath) // 'productImage' must match your Multer config
.expect(200);
// Assert the response body for a success message and file information
expect(res.body.message).toBe('File uploaded successfully!');
expect(res.body.fileInfo).toBeDefined();
expect(res.body.fileInfo.fieldname).toBe('productImage');
});
// Challenge: Now, write tests for the error cases.
// Test for invalid file type
it('should return 400 when uploading an invalid file type', async () => {
const filePath = path.join(__dirname, 'fixtures', 'test-document.txt');
const res = await request(app)
.post('/api/upload')
.attach('productImage', filePath)
.expect(400);
// Assert the specific error message from your fileFilter
expect(res.body.error).toBe('Invalid file type. Only JPEG and PNG are allowed.');
});
// Test for request with no file attached
it('should return 400 when no file is uploaded', async () => {
const res = await request(app)
.post('/api/upload')
// Note: We are NOT calling .attach() here
.expect(400);
// Assert the specific error message for a missing file
expect(res.body.error).toBe('No file was uploaded. Please select a file to upload.');
});
});
Run Your Tests
Open your terminal and run your test command:
npm test
You should see your new upload.test.js suite execute, and all three tests should pass. This confirms that your endpoint correctly handles success, invalid types, and missing files, and that your automated tests can prove it.
Conclusion
Congratulations! You have successfully written a comprehensive set of integration tests for your file upload endpoint. You've mastered the use of Supertest's .attach() method, which is the standard for testing file uploads in Node.js applications. By creating tests for both the "happy path" (successful upload) and critical error cases, you've made your API significantly more robust and maintainable.
Key Takeaways:
- Fixtures are Essential: Always include test files (fixtures) within your project to ensure tests are self-contained and portable.
- Use
path.join: Construct file paths usingpath.join(__dirname, ...)to make your tests reliable and runnable from any location. - Master
.attach(fieldName, filePath): This is the core Supertest method for simulating file uploads inmultipart/form-datarequests. - Test All Scenarios: A complete test suite covers not just success cases but also predictable failures like invalid input or missing data.
Next Up:
Your API is now well-structured, functional, and automatically tested. The next step is to make it easy for other developers (or your exam reviewer) to use and understand. In the next lesson, we will dive into Postman. You will learn how to create a shareable Postman collection for your API, using environment variables to make your requests reusable—a key deliverable for project submissions and professional collaboration.