Project 12: Automated Test Suite for the IoT Backend
Configure a Jest and Supertest integration environment to validate API route handlers and validation middlewares.
Domain
Automated Integration Testing
Difficulty
⭐⭐⭐⭐☆ (Advanced)
Course Module
Testing & Validation (Node.js/TS)
Deliverables
Jest Config File, Integration Test Suite
1. Test Suite Execution Lifecycle
To verify API endpoints reliably, we use automated integration tests instead of manual curl commands. The diagram below details the test execution lifecycle. The Jest harness launches a mock Express server, compiles the TypeScript files in memory using `ts-jest`, executes test cases sequentially, uses Supertest to query routes, asserts HTTP status responses, and shuts down the server.
2. Part 1: Step-by-Step Individual Test Configurations & Commands
Follow these detailed steps to install the test dependencies, configure Jest, and run the test suite inside your VM guest.
STEP 1
Install Jest, ts-jest, and Supertest Dependencies
Open a terminal window inside the Ubuntu VM. Navigate to the project directory and install the testing packages as devDependencies.
ubuntu@iot-vm:~$ cd ~/workspace/backend_scaffold && npm install -D jest @types/jest ts-jest supertest @types/supertest
We run `npm install -D` to download Jest (test runner), Supertest (HTTP assertions), and `ts-jest` to compile TypeScript test files in memory.
STEP 2
Generate Jest Configuration File
Create a Jest configuration file to define how tests are executed and compiled.
We open `api.test.ts` in `gedit` using the `&` operator to run the text editor in the background, keeping the terminal prompt active.
STEP 5
Register Test Scripts in package.json
Edit `package.json` to configure the test scripts and code coverage options.
Settings to update (gedit package.json):
1. Locate the "scripts" block.
2. Replace '"test": "echo \"Error: no test specified\" && exit 1"' with:
'"test": "jest --runInBand --detectOpenHandles --forceExit"'
3. Add a coverage script:
'"coverage": "jest --coverage"'
4. Save and exit the editor.
We add the test scripts to `package.json`, passing the `--runInBand` and `--detectOpenHandles` flags to ensure tests run sequentially and open connections are closed.
STEP 6
Execute the Automated Test Suite
Run the test suite in the VM terminal to verify the API endpoints and validation middleware.
ubuntu@iot-vm:~/workspace/backend_scaffold$ npm run test
We run `npm run test` to execute the Jest test runner, validating that all HTTP assertions and schema validation checks pass.
STEP 7
Generate Code Coverage Reports
Run the coverage script in the terminal to measure code coverage.
ubuntu@iot-vm:~/workspace/backend_scaffold$ npm run coverage
We run `npm run coverage` to generate a coverage report, analyzing what percentage of route handlers and validation statements are covered by tests.
3. Mock Jest Code Coverage Console Dashboard Representation
The diagram below represents the Jest code coverage report, displaying the statement, branch, function, and line coverage metrics for the backend API.
4. Part 2: Complete Integration Test Suite Code
Copy the integration test file code below and save it to your project folder to complete the test suite configuration.
Asset 1: Integration Test Suite File (`src/tests/api.test.ts`)
Line-by-Line Code Breakdown
import request from 'supertest';: Imports Supertest, which provides assertions for testing HTTP endpoints.
import express from 'express';: Imports Express to initialize a mock server instance during testing.
import apiRouter from '../routes/api';: Imports the router containing target routes to bind it to the mock server.
describe('IoT Backend API Integration Tests', ...): Groups related integration tests into a single test suite.
request(app).get('/api/devices'): Executes a mock HTTP GET call to retrieve the registered devices list.
expect(res.status).toBe(200);: Asserts that the response HTTP status code is 200 OK.
expect(Array.isArray(res.body)).toBe(true);: Asserts that the response payload is formatted as a JSON array.
request(app).post('/api/telemetry').send(...): Executes a mock HTTP POST call, sending a telemetry payload to verify the endpoint.
expect(res.body.error).toBeDefined();: Asserts that the error property is present in the response body when sending invalid payloads.
// Setup Project 12: Automated Integration Test Suiteimportrequestfrom'supertest';
importexpressfrom'express';
importapiRouterfrom'../routes/api';
constapp = express();
app.use(express.json());
app.use('/api', apiRouter);
describe('IoT Backend API Integration Tests', () => {
// Test Case 1: GET /api/devicesit('should retrieve a list of registered devices with status 200 OK', async () => {
constres = awaitrequest(app)
.get('/api/devices')
.set('Accept', 'application/json');
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body[0].id).toBe('node_1');
});
// Test Case 2: POST /api/telemetry (Success path)it('should accept valid telemetry readings with status 201 Created', async () => {
constvalidPayload = {
deviceId: 'node_1',
temperature: 24.5,
light: 500
};
constres = awaitrequest(app)
.post('/api/telemetry')
.send(validPayload)
.set('Content-Type', 'application/json');
expect(res.status).toBe(201);
expect(res.body.message).toBe('Telemetry saved successfully');
expect(res.body.reading.temperature).toBe(24.5);
});
// Test Case 3: POST /api/telemetry (Validation Failure path)it('should reject invalid telemetry values with status 400 Bad Request', async () => {
constinvalidPayload = {
deviceId: 'node_1',
temperature: 'very_hot', // Should fail validation (expects float number)light: 500
};
constres = awaitrequest(app)
.post('/api/telemetry')
.send(invalidPayload)
.set('Content-Type', 'application/json');
expect(res.status).toBe(400);
expect(res.body.error).toBeDefined();
});
});
5. Deliverables Summary
Created Workspace Assets
TypeScript test file: src/tests/api.test.ts.
Jest runtime configuration parameters: jest.config.js.