├── .env.example ├── .gitignore ├── README.md ├── server.js ├── models └── product.model.js ├── app.js ├── routes └── product.route.js ├── package.json ├── tests └── product.test.js └── controllers └── product.controller.js /.env.example: -------------------------------------------------------------------------------- 1 | MONGODB_URI= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jest-tests-demo 2 | Tutorial on how to write tests for expressjs and mongoose application 3 | 4 | ![how to write tests](https://user-images.githubusercontent.com/70439799/193745664-bd9c4693-e1c8-46f8-8867-98130544d0ef.png) 5 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const app = require("./app"); 3 | const PORT = process.env.PORT || 5000; 4 | 5 | require("dotenv").config(); 6 | 7 | /* Connecting to the database and then starting the server. */ 8 | mongoose 9 | .connect(process.env.MONGODB_URI) 10 | .then(() => { 11 | app.listen(PORT, console.log("Server started on port 5000")); 12 | }) 13 | .catch((err) => { 14 | console.log(err); 15 | }); 16 | -------------------------------------------------------------------------------- /models/product.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | /* This is creating a new schema for the product model. */ 6 | const productSchema = new Schema( 7 | { 8 | name: { 9 | type: String, 10 | required: true, 11 | }, 12 | price: { 13 | type: Number, 14 | required: true, 15 | }, 16 | description: { 17 | type: String, 18 | required: true, 19 | }, 20 | }, 21 | { timestamps: true } 22 | ); 23 | 24 | module.exports = mongoose.model("Product", productSchema); 25 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | const ProductRoutes = require("./routes/product.route"); 4 | 5 | const app = express(); 6 | 7 | /* A middleware that parses the body of the request and makes it available in the req.body object. */ 8 | app.use(express.json()); 9 | 10 | /* This is the root route. It is used to check if the server is running. */ 11 | app.get("/", (req, res) => { 12 | res.status(200).json({ alive: "True" }); 13 | }); 14 | 15 | /* Telling the server to use the routes in the ProductRoutes file. */ 16 | app.use("/api", ProductRoutes); 17 | 18 | module.exports = app; 19 | -------------------------------------------------------------------------------- /routes/product.route.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | const { 4 | getProducts, 5 | getProduct, 6 | createProduct, 7 | updateProduct, 8 | deleteProduct, 9 | } = require("../controllers/product.controller"); 10 | 11 | const router = express.Router(); 12 | 13 | /* Creating the routes for the product controller. */ 14 | router.get("/products", getProducts); 15 | 16 | router.get("/products/:id", getProduct); 17 | 18 | router.post("/products", createProduct); 19 | 20 | router.patch("/products/:id", updateProduct); 21 | 22 | router.delete("/products/:id", deleteProduct); 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tutorial", 3 | "version": "1.0.0", 4 | "description": "Tutorial on how to write test for expressjs and mongoose applications", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "cross-env NODE_ENV=test jest --testTimeout=5000", 8 | "start": "node server.js", 9 | "dev": "nodemon server.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/itsrakeshhq/jest-tests-demo.git" 14 | }, 15 | "keywords": [ 16 | "testing", 17 | "expressjs", 18 | "nodejs", 19 | "mongodb", 20 | "mongoose", 21 | "jest", 22 | "supertest" 23 | ], 24 | "author": "Rakesh Potnuru", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/itsrakeshhq/jest-tests-demo.git/issues" 28 | }, 29 | "homepage": "https://github.com/itsrakeshhq/jest-tests-demo.git#readme", 30 | "dependencies": { 31 | "axios": "^0.27.2", 32 | "dotenv": "^16.0.1", 33 | "express": "^4.18.1", 34 | "mongoose": "^6.4.4" 35 | }, 36 | "devDependencies": { 37 | "cross-env": "^7.0.3", 38 | "jest": "^28.1.3", 39 | "supertest": "^6.2.4" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/product.test.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const request = require("supertest"); 3 | const app = require("../app"); 4 | 5 | require("dotenv").config(); 6 | 7 | /* Connecting to the database before each test. */ 8 | beforeEach(async () => { 9 | await mongoose.connect(process.env.MONGODB_URI); 10 | }); 11 | 12 | /* Dropping the database and closing connection after each test. */ 13 | afterEach(async () => { 14 | // await mongoose.connection.dropDatabase(); 15 | await mongoose.connection.close(); 16 | }); 17 | 18 | /* Testing the API endpoints. */ 19 | describe("GET /api/products", () => { 20 | it("should return all products", async () => { 21 | const res = await request(app).get("/api/products"); 22 | expect(res.statusCode).toBe(200); 23 | expect(res.body.length).toBeGreaterThan(0); 24 | }); 25 | }); 26 | 27 | describe("GET /api/products/:id", () => { 28 | it("should return a product", async () => { 29 | const res = await request(app).get( 30 | "/api/products/6331abc9e9ececcc2d449e44" 31 | ); 32 | expect(res.statusCode).toBe(200); 33 | expect(res.body.name).toBe("Product 1"); 34 | }); 35 | }); 36 | 37 | describe("POST /api/products", () => { 38 | it("should create a product", async () => { 39 | const res = await request(app).post("/api/products").send({ 40 | name: "Product 2", 41 | price: 1009, 42 | description: "Description 2", 43 | }); 44 | expect(res.statusCode).toBe(201); 45 | expect(res.body.name).toBe("Product 2"); 46 | }); 47 | }); 48 | 49 | describe("PUT /api/products/:id", () => { 50 | it("should update a product", async () => { 51 | const res = await request(app) 52 | .patch("/api/products/6331abc9e9ececcc2d449e44") 53 | .send({ 54 | name: "Product 4", 55 | price: 104, 56 | description: "Description 4", 57 | }); 58 | expect(res.statusCode).toBe(200); 59 | expect(res.body.price).toBe(104); 60 | }); 61 | }); 62 | 63 | describe("DELETE /api/products/:id", () => { 64 | it("should delete a product", async () => { 65 | const res = await request(app).delete( 66 | "/api/products/6331abc9e9ececcc2d449e44" 67 | ); 68 | expect(res.statusCode).toBe(200); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /controllers/product.controller.js: -------------------------------------------------------------------------------- 1 | const Product = require("../models/product.model"); 2 | 3 | /** 4 | * It's an asynchronous function that uses the await keyword to wait for the result of the find() 5 | * method on the Product model. 6 | * 7 | * The find() method returns a promise, which is why we can use the await keyword. 8 | * @param req - The request object. This object represents the HTTP request and has properties for the 9 | * request query string, parameters, body, HTTP headers, and so on. 10 | * @param res - The response object. 11 | */ 12 | const getProducts = async (req, res) => { 13 | try { 14 | const products = await Product.find(); 15 | res.status(200).json(products); 16 | } catch (error) { 17 | res.status(500).json(error); 18 | } 19 | }; 20 | 21 | /** 22 | * It's an asynchronous function that uses the Product model to find a product by its id, and then 23 | * sends a response with the product's data. 24 | * @param req - The request object. 25 | * @param res - The response object. 26 | */ 27 | const getProduct = async (req, res) => { 28 | try { 29 | const product = await Product.findById(req.params.id); 30 | res.status(200).json(product); 31 | } catch (error) { 32 | res.status(500).json(error); 33 | } 34 | }; 35 | 36 | /** 37 | * It creates a new product using the data from the request body and returns the created product in the 38 | * response. 39 | * @param req - The request object. This object represents the HTTP request and has properties for the 40 | * request query string, parameters, body, HTTP headers, and so on. 41 | * @param res - The response object. 42 | */ 43 | const createProduct = async (req, res) => { 44 | try { 45 | const product = await Product.create(req.body); 46 | res.status(201).json(product); 47 | } catch (error) { 48 | res.status(500).json(error); 49 | } 50 | }; 51 | 52 | /** 53 | * It takes the id of the product to be updated from the request params, and the updated product data 54 | * from the request body, and then updates the product in the database with the new data, and returns 55 | * the updated product to the client. 56 | * @param req - The request object. 57 | * @param res - The response object. 58 | */ 59 | const updateProduct = async (req, res) => { 60 | try { 61 | const product = await Product.findByIdAndUpdate(req.params.id, req.body, { 62 | new: true, 63 | }); 64 | res.status(200).json(product); 65 | } catch (error) { 66 | res.status(500).json(error); 67 | } 68 | }; 69 | 70 | /** 71 | * It finds a product by its id and deletes it. 72 | * @param req - The request object. This object represents the HTTP request and has properties for the 73 | * request query string, parameters, body, HTTP headers, and so on. 74 | * @param res - The response object. 75 | */ 76 | const deleteProduct = async (req, res) => { 77 | try { 78 | const product = await Product.findByIdAndDelete(req.params.id); 79 | res.status(200).json(product); 80 | } catch (error) { 81 | res.status(500).json(error); 82 | } 83 | }; 84 | 85 | module.exports = { 86 | getProducts, 87 | getProduct, 88 | createProduct, 89 | updateProduct, 90 | deleteProduct, 91 | }; 92 | --------------------------------------------------------------------------------