Search code examples
node.jsreactjsexpressunit-testingmern

Getting an error "For async tests and hooks, ensure "done()" is called; "


I am trying to build a MERN website and implement unit tests for its POST method. But it gives me the error "For async tests and hooks, ensure "done()" is called;" ? Anyone can you please give me some idea why does this error occur? and How can I resolve this issue?

error:

  1. POST /products OK, creating a new product works: Error: Timeout of 30000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/home/nimeshaf/Documents/mernPro/MERN-project/test/api/product/post.js) at listOnTimeout (node:internal/timers:557:17)

I have attached my files below

MERNProject/server.js

const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const cors = require("cors");

const app = express();

//import routes
const postRoutes = require("./routes/products");

//app middleware
app.use(bodyParser.json());
app.use(cors());

app.use(postRoutes);

const PORT = 8000;
const DB_URL =
  "mongodb+srv://twg:[email protected]/myFirstDatabase?retryWrites=true&w=majority";

function connect() {
  return new Promise((resolve, reject) => {
    mongoose
      .connect(DB_URL)
      .then((res, err) => {
        if (err) return reject(err);
        //console.log(`DB connected`);
        resolve();
      })
      .catch((err) => console.log(`DB connection error`, err));
  });
}

function close() {
  return mongoose.disconnect();
}

connect();

app.listen(PORT, () => {
  console.log(`App is running on ${PORT}`);
});

module.exports = { connect, close };

MERNProject/test/api/products/post.js

const expect = require("chai").expect;
const request = require("supertest");

const app = require("../../../routes/products.js");
const conn = require("../../../server.js");

describe("POST /products", () => {
  before((done) => {
    conn
      .connect()
      .then(() => done())
      .catch((err) => done(err));
  });

  after((done) => {
    conn
      .close()
      .then(() => done())
      .catch((err) => done(err));
  });

  it("OK, creating a new product works", (done) => {
    request(app)
      .post("/product/save")
      .send({
        productName: "Mango",
        description: "description Mango",
        price: "rs 50",
        productCategory: "fruits",
        productUrl:
          "https://upload.wikimedia.org/wikipedia/commons/9/90/Hapus_Mango.jpg",
      })
      .then((res) => {
        const body = res.body;

        expect(body).to.contain.porperty("_id");
        expect(body).to.contain.porperty("productName");
        expect(body).to.contain.porperty("description");
        expect(body).to.contain.porperty("price");
        expect(body).to.contain.porperty("productCategory");
        expect(body).to.contain.porperty("productUrl");
        done();
      });
  });
});

MERNProject/routes/products.js

const express = require("express");
const Products = require("../models/products");

const router = express();

//save Products
router.post("/product/save", (err, req, res, next) => {
  let newProduct = new Products(req.body);

  newProduct.save((err) => {
    if (err) {
      return res.status(400).json({
        error: err,
      });
    }
    return res.status(200).json({
      success: "Product saved successfully",
    });
  });
});

//get products
router.get("/products", (req, res) => {
  Products.find().exec((err, products) => {
    if (err) {
      return res.status(400).json({
        error: err,
      });
    }
    return res.status(200).json({
      success: true,
      existingProducts: products,
    });
  });
});

//update products
router.put("/product/update/:id", (req, res) => {
  Products.findByIdAndUpdate(
    req.params.id,
    {
      $set: req.body,
    },
    (err, product) => {
      if (err) {
        return res.status(400).json({ error: err });
      }

      return res.status(200).json({
        success: "Updated Successfully",
      });
    }
  );
});

//delete product
router.delete("/product/delete/:id", (req, res) => {
  Products.findByIdAndRemove(req.params.id).exec((err, deleteProduct) => {
    if (err)
      return res.status(400).json({
        message: "Delete unsuccessful",
        err,
      });

    return res.json({
      message: "Delete Successfully",
      deleteProduct,
    });
  });
});

//get specific product
router.get("/product/:id", (req, res) => {
  let productId = req.params.id;

  Products.findById(productId, (err, product) => {
    if (err) {
      return res.status(400).json({ success: false, err });
    }

    return res.status(200).json({
      success: true,
      product,
    });
  });
});

module.exports = router;

MERNProject/package.json

{
  "name": "mern_crud",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "server": "nodemon server.js",
    "client": "npm run start --prefix client",
    "dev": "concurrently \"npm run server\" \"npm run client\"",
    "test": "mocha --recursive --timeout 30000 --exit"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.2",
    "chai": "^4.3.6",
    "concurrently": "^7.0.0",
    "cors": "^2.8.5",
    "express": "^4.17.3",
    "mocha": "^9.2.2",
    "mockgoose": "^8.0.4",
    "mongoose": "^6.2.7",
    "mongose": "^0.0.2-security",
    "nodemon": "^2.0.15",
    "react-router-dom": "^6.2.2",
    "supertest": "^6.2.2"
  }
}

Solution

  • Your issue probably comes from the spec OK, creating a new product works where the done callback is not called in case of failure in the promises chain. You need to add a catch:

        request(app)
          .post("/product/save")
          .send(/* ... */)
          .then((res) => {
            // ...
            done();
          })
          .catch((err) => done(err)); // this is missing
    

    That being said, I would strongly recommend using the async/await syntax instead of the done callback:

    it("OK, creating a new product works", async () => {
      const res = await request(app)
        .post("/product/save")
        .send(/* ... */);
    
      const body = res.body;
      expect(body).to.contain.porperty("_id");
      expect(body).to.contain.porperty("productName");
      expect(body).to.contain.porperty("description");
      expect(body).to.contain.porperty("price");
      expect(body).to.contain.porperty("productCategory");
      expect(body).to.contain.porperty("productUrl");
    });