Mocking Node-Fetch Responses in TypeScript Using Supertest and Vitest
In the realm of testing Node.js applications, especially those involving network requests, accurately simulating external API responses is crucial. This ensures reliable unit testing by isolating the code under scrutiny from external dependencies. This blog post will guide you through effectively mocking Node-fetch responses in TypeScript, leveraging the powerful combination of Supertest and Vitest.
Understanding the Need for Mocking
Mocking is a technique commonly employed in software testing to isolate and control the behavior of external components, such as network requests or database interactions. In the context of Node.js applications using node-fetch for API calls, mocking these requests allows us to:
- Control Response Data: Simulate different responses, including success, failure, and edge cases.
- Isolate Unit Tests: Ensure tests focus on the specific logic under examination, without relying on external API availability.
- Improve Test Speed: Avoid the overhead of real network requests, leading to faster test execution.
Setting Up the Test Environment
Before diving into mocking, ensure you have the necessary tools installed:
npm install --save-dev supertest vitest @types/supertest @types/node-fetch Mocking Node-Fetch with Supertest and Vitest
Let's demonstrate how to mock node-fetch responses using Supertest and Vitest within a TypeScript project. Assume we have a simple Express.js application with a route that fetches data from an external API:
1. The Application Route
import express from "express"; import fetch from "node-fetch"; const app = express(); app.get("/api/data", async (req, res) => { try { const response = await fetch("https://api.example.com/data"); const data = await response.json(); res.json(data); } catch (error) { res.status(500).send("Error fetching data"); } }); export default app; 2. The Test File
import request from "supertest"; import { describe, expect, it, vi } from "vitest"; import app from "./app"; describe("API Routes", () => { it("should fetch data from the API", async () => { // Mock the fetch response vi.mock("node-fetch", () => ({ default: jest.fn().mockResolvedValue({ json: () => Promise.resolve({ data: "Mocked Data" }), }), })); const response = await request(app).get("/api/data"); expect(response.status).toBe(200); expect(response.body).toEqual({ data: "Mocked Data" }); }); }); In this example, we use Vitest's vi.mock function to replace the node-fetch module with a custom mock. This mock resolves to a function that returns a promise with a predefined JSON response. Supertest, then, makes a request to the app's /api/data endpoint, allowing us to assert the expected response.
3. Handling Different Response Scenarios
We can further enhance the testing by simulating various scenarios, such as API errors or different status codes.
describe("API Routes", () => { it("should handle API errors", async () => { vi.mock("node-fetch", () => ({ default: jest.fn().mockRejectedValue(new Error("API Error")), })); const response = await request(app).get("/api/data"); expect(response.status).toBe(500); expect(response.text).toContain("Error fetching data"); }); it("should handle different status codes", async () => { vi.mock("node-fetch", () => ({ default: jest.fn().mockResolvedValue({ status: 404, json: () => Promise.resolve({ message: "Not Found" }), }), })); const response = await request(app).get("/api/data"); expect(response.status).toBe(404); expect(response.body).toEqual({ message: "Not Found" }); }); }); Comparison: Supertest vs. Jest
While Supertest is a popular choice for testing Express.js applications, it's not the only option. Jest, another renowned testing framework, provides its own methods for mocking and testing HTTP requests. Here's a table comparing Supertest and Jest:
| Feature | Supertest | Jest |
|---|---|---|
| Purpose | Testing Express.js applications | General-purpose JavaScript testing |
| Mocking | Built-in mocking for HTTP requests | Requires manual mocking with jest.fn() or jest.mock() |
| Ease of Use | Simple and intuitive API for testing routes | More flexibility, but can be more complex for HTTP tests |
| Community Support | Strong community and extensive documentation | Large and active community with comprehensive support |
The choice between Supertest and Jest often depends on project context and preferences. Supertest excels in testing Express.js applications, while Jest offers broader capabilities for general JavaScript testing.
Testing with Mocking: A Comprehensive Example
To demonstrate how to mock node-fetch with Supertest and Vitest in a more practical scenario, let's imagine a user registration API endpoint. Here's how we would test the registration logic, ensuring successful user creation, handling errors, and mocking the external API call:
1. The Application Route (Registration)
import express from "express"; import fetch from "node-fetch"; const app = express(); app.post("/api/users", async (req, res) => { const { username, email } = req.body; try { const response = await fetch("https://api.external.com/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, email }), }); if (response.ok) { const data = await response.json(); res.status(201).json(data); } else { res.status(response.status).json({ error: "Registration failed" }); } } catch (error) { res.status(500).send("Error registering user"); } }); export default app; 2. The Test File (Registration Test)
import request from "supertest"; import { describe, expect, it, vi } from "vitest"; import app from "./app"; describe("User Registration", () => { it("should successfully register a user", async () => { vi.mock("node-fetch", () => ({ default: jest.fn().mockResolvedValue({ ok: true, status: 201, json: () => Promise.resolve({ message: "User successfully registered", userId: 123, }), }), })); const response = await request(app) .post("/api/users") .send({ username: "testuser", email: "test@example.com" }); expect(response.status).toBe(201); expect(response.body).toEqual({ message: "User successfully registered", userId: 123, }); }); it("should handle API errors", async () => { vi.mock("node-fetch", () => ({ default: jest.fn().mockRejectedValue(new Error("API Error")), })); const response = await request(app) .post("/api/users") .send({ username: "testuser", email: "test@example.com" }); expect(response.status).toBe(500); expect(response.text).toContain("Error registering user"); }); }); This example demonstrates how to mock various responses, simulating successful registration, API errors, and error handling within the application code.
Conclusion
Mocking Node-fetch responses with Supertest and Vitest is an essential technique for writing robust and efficient unit tests for Node.js applications. By effectively isolating and controlling external dependencies, you gain valuable insights into your application's behavior, leading to improved code quality and confidence. Remember that using these tools to test your API endpoints will save you time and frustration in the long run, ensuring that your application is well-tested and reliable.
For further exploration of testing techniques and best practices, you might find this article helpful: is there is a way to check server cpu and memory usage after doing performance test using karate framework.
⚡️Ejemplo de VITEST MOCK FETCH (TypeScript) FÁCIL y RÁPIDO | Vitest Mock 2024
⚡️Ejemplo de VITEST MOCK FETCH (TypeScript) FÁCIL y RÁPIDO | Vitest Mock 2024 from Youtube.com