0

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,ssmith@emailtest.com

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".

gdubs
  • 2,724
  • 9
  • 55
  • 102
  • First of all, you shouldn't mix `async` and `done`, it's either one or another, preferably the former. And the problem is that `/post_file` works incorrectly, it's asynchronous but it responds synchronously with wrong response because it doesn't wait for `get_output`. – Estus Flask Jul 29 '20 at 20:03
  • @EstusFlask so how do i make it wait? do i just make it a promise? – gdubs Jul 30 '20 at 06:09
  • Yes, promisify /post_file route and a stream in get_output. – Estus Flask Jul 30 '20 at 06:29
  • Do you mind writing a snippet of that? I'm a little new to node and still getting a hang of it. Thanks – gdubs Jul 30 '20 at 06:38
  • To promisify a stream, you need to know what events it supports. I can't write a snippet without knowing what is `csv` and how it works. – Estus Flask Jul 30 '20 at 07:03
  • it's fast-csv. ive updated the question with the complete dependencies – gdubs Jul 30 '20 at 07:13

1 Answers1

1

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")
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565