Search code examples
node.jsjestjssupertestfast-csv

nodejs with supertest, calling an endpoint twice for different test cases getting a Did you forget to wait for something async in your test?


i have a single endpoint where i call twice on 2 different describes to test different responses

const express = require("express");
const router = express.Router();
const fs = require("fs");
const multer = require("multer");
const upload = multer({ dest: "files/" });
const csv = require("fast-csv");

let response = { message: "success" }
router.post("/post_file", upload.single("my_file"), (req, res) => {
    let output = get_output(req.file.path);
    fs.unlinkSync(req.file.path);

    if(output.errors.length > 0) response.message = "errors found";

    res.send(JSON.stringify(response))
})


const get_output = (path) => {

  let errors = []
  let fs_stream = fs.createReadStream(path);
  let csv_stream = csv.parse().on("data", obj => {
                   if(!is_valid(obj)) errors.push(obj);
            });
  

  fs_stream.pipe(csv_stream);

  return {errors};
}

const is_valid = (row) => {
   console.log("validate row")
   
   // i validate here and return a bool
}

my unit tests

const app = require("../server");
const supertest = require("supertest");
const req = supertest(app);

describe("parent describe", () => {

  describe("first call", () => {   
      const file = "my path to file"

      // this call succeeds
    it("should succeed", async (done) => {
      let res = await req
        .post("/post_file")
        .attach("my_file", file);
     
      expect(JSON.parse(res.text).message).toBe("success")
      done();
     });
   })

    describe("second call", () => {   
      const file = "a different file"

    // this is where the error starts
    it("should succeed", async (done) => {
      let res = await req
        .post("/post_file")
        .attach("my_file", file);
     
      expect(JSON.parse(res.text).message).toBe("errors found")
      done();
     });
   })
})

// csv file is this

NAME,ADDRESS,EMAIL
Steve Smith,35 Pollock St,[email protected]

I get the following

Cannot log after tests are done. Did you forget to wait for something async in your test? Attempted to log "validate row".


Solution

  • The problem is that tested route is incorrectly implemented, it works asynchronously but doesn't wait for get_output to end and responds synchronously with wrong response. The test just reveals that console.log is asynchronously called after test end.

    Consistent use of promises is reliable way to guarantee the correct execution order. A stream needs to be promisified to be chained:

    router.post("/post_file", upload.single("my_file"), async (req, res, next) => {
      try {
        let output = await get_output(req.file.path);
        ...
        res.send(JSON.stringify(response))
      } catch (err) {
        next(err)
      }
    })    
    
    const get_output = (path) => {
      let errors = []
      let fs_stream = fs.createReadStream(path);
      return new Promise((resolve, reject) => {
        let csv_stream = csv.parse()
        .on("data", obj => {...})
        .on("error", reject)
        .on("end", () => resolve({errors}))
      });
    }
    

    async and done shouldn't be mixed in tests because they serve the same goal, this may result in test timeout if done is unreachable:

    it("should succeed", async () => {
      let res = await req
        .post("/post_file")
        .attach("my_file", file);
     
      expect(JSON.parse(res.text).message).toBe("success")
    });