Help with issue tracker, chai/mocha test error, app crashing

Hello there.

I´m coding my issue tracker project from the Quality Assurance section.

I think my API is done, I´ve started with the tests. I´ve done 12 from 14 and an error that I don´t understand came up.

My console shows the following:

[nodemon] starting `node server.js`
Your app is listening on port 3000
Running Tests...


  Functional Tests
    POST request to api/issues/{project}
      √ Create an issue with every field
creating new issue
      √ Create an issue with only required fields
creating new issue
      √ Create an issue with missing required fields
    GET request to api/issues/{project}
      √ View issues on a project
Issue created succesfully
      √ View issues on a project with one filter
      √ View issues on a project with multiple filters
    PUT request to api/issues/{project}
      √ Update one field on an issue
updating issue
      √ Update multiple fields on an issue
updating issue
      √ Update an issue with missing _id
      √ Update an issue with no fields to update
updating issue
      √ Update an issue with an invalid _id
updating issue
    DELETE request to api/issues/{project}
      √ Delete an issue
deleting issue


  12 passing (184ms)

Issue created succesfully

C:\Users\Ting-Ting\Desktop\Desarrollo web\FCC\Project Issue Tracker\node_modules\mocha\lib\runner.js:962
    throw err;
    ^
AssertionError: expected undefined to equal 'succesfully updated'
    at C:\Users\Ting-Ting\Desktop\Desarrollo web\FCC\Project Issue Tracker\tests\2_functional-tests.js:152:18
    at Test.Request.callback (C:\Users\Ting-Ting\Desktop\Desarrollo web\FCC\Project Issue Tracker\node_modules\superagent\lib\node\index.js:716:12)
    at C:\Users\Ting-Ting\Desktop\Desarrollo web\FCC\Project Issue Tracker\node_modules\superagent\lib\node\index.js:916:18
    at IncomingMessage.<anonymous> (C:\Users\Ting-Ting\Desktop\Desarrollo web\FCC\Project Issue Tracker\node_modules\superagent\lib\node\parsers\json.js:19:7)
    at IncomingMessage.emit (events.js:412:35)
    at endReadableNT (internal/streams/readable.js:1334:12)
    at processTicksAndRejections (internal/process/task_queues.js:82:21) {
  showDiff: true,
  actual: undefined,
  expected: 'succesfully updated',
  operator: 'strictEqual'
}
[nodemon] app crashed - waiting for file changes before starting...

My tests looks like this:

const chaiHttp = require("chai-http");
const chai = require("chai");
const assert = chai.assert;
const server = require("../server");
const { request } = require("chai");

chai.use(chaiHttp);
let testId;

describe("Functional Tests", function () {
  // POST request
  describe("POST request to api/issues/{project}", function () {
    it("Create an issue with every field", function (done) {
      chai
        .request(server)
        .post("/api/issues/projects")
        .send({
          issue_title: "Project issue",
          issue_text: "Not passing tests",
          created_by: "Ting-Ting",
          assigned_to: "FCC",
          status_text: "Not solved",
        })
        .end(function (err, res) {
          testId = res.body._id;
          assert.equal(res.status, 200);
          assert.equal(res.body.issue_title, "Project issue");
          assert.equal(res.body.issue_text, "Not passing tests");
          assert.equal(res.body.created_by, "Ting-Ting");
          assert.equal(res.body.assigned_to, "FCC");
          assert.equal(res.body.status_text, "Not solved");
        });
      done();
    });
    it("Create an issue with only required fields", function (done) {
      chai
        .request(server)
        .post("/api/issues/projects")
        .send({
          issue_title: "Project issue",
          issue_text: "Not passing tests",
          created_by: "Ting-Ting",
          assigned_to: "",
          status_text: "",
        })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          assert.equal(res.body.issue_title, "Project issue");
          assert.equal(res.body.issue_text, "Not passing tests");
          assert.equal(res.body.created_by, "Ting-Ting");
          assert.equal(res.body.assigned_to, "");
          assert.equal(res.body.status_text, "");
        });
      done();
    });
    it("Create an issue with missing required fields", function (done) {
      chai
        .request(server)
        .post("/api/issues/projects")
        .send({
          issue_title: "",
          issue_text: "",
          created_by: "",
          assigned_to: "",
          status_text: "",
        })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          assert.equal(res.body.error, "required field(s) missing");
        });
      done();
    });
  });
  // GET requests
  describe("GET request to api/issues/{project}", function () {
    it("View issues on a project", function (done) {
      chai
        .request(server)
        .get("/api/issues/project")
        .end(function (err, res) {
          assert.equal(res.status, 200);
          res.body.forEach((issue) => {
            assert.notEqual(issue._id, undefined);
            assert.notEqual(issue.issue_title, undefined);
            assert.notEqual(issue.issue_text, undefined);
            assert.notEqual(issue.created_by, undefined);
            assert.notEqual(issue.assigned_to, undefined);
            assert.notEqual(issue.status_text, undefined);
            assert.notEqual(issue.created_on, undefined);
            assert.notEqual(issue.updated_on, undefined);
            assert.notEqual(issue.open, undefined);
          });
        });
      done();
    });
    it("View issues on a project with one filter", function (done) {
      chai
        .request(server)
        .get("/api/issues/project")
        .query({ assigned_to: "FCC" })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          res.body.forEach((issue) => {
            assert.equal(issue.assigned_to, "FCC");
          });
        });
      done();
    });
    it("View issues on a project with multiple filters", function (done) {
      chai
        .request(server)
        .get("/api/issues/project")
        .query({ assigned_to: "FCC", created_by: "Ting-Ting" })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          res.body.forEach((issue) => {
            assert.equal(issue.assigned_to, "FCC");
            assert.equal(issue.created_by, "Ting-Ting");
          });
        });
      done();
    });
  });
  // PUT requests
  describe("PUT request to api/issues/{project}", function () {
    it("Update one field on an issue", function (done) {
      chai
        .request(server)
        .put("/api/issues/project")
        .send({
          _id: testId,
          issue_text: "Problem XYZ",
        })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          assert.equal(res.body.result, "succesfully updated");
          assert.equal(res.body._id, testId);
        });
      done();
    });
    it("Update multiple fields on an issue", function (done) {
      chai
        .request(server)
        .put("/api/issues/project")
        .send({
          _id: testId,
          issue_text: "Problem XYZ",
          status_text: "Solved",
        })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          assert.equal(res.body.result, "succesfully updated");
          assert.equal(res.body._id, testId);
        });
      done();
    });
    it("Update an issue with missing _id", function (done) {
      chai
        .request(server)
        .put("/api/issues/project")
        .send({
          _id: "",
        })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          assert.equal(res.body.error, "missing _id");
        });
      done();
    });
    it("Update an issue with no fields to update", function (done) {
      chai
        .request(server)
        .put("/api/issues/project")
        .send({
          _id: testId,
          issue_title: "",
          issue_text: "",
          created_by: "",
          assigned_to: "",
          status_text: "",
        })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          assert.equal(res.body.error, "no update field(s) sent");
        });
      done();
    });
    it("Update an issue with an invalid _id", function (done) {
      chai
        .request(server)
        .put("/api/issues/project")
        .send({
          _id: "nonexistingid",
          created_by: "Ting-Ting",
        })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          assert.equal(res.body.error, "could not update");
          assert.equal(res.body._id, "nonexistingid");
        });
      done();
    });
  });

  // DELETE request
  describe("DELETE request to api/issues/{project}", function () {
    it("Delete an issue", function (done) {
      chai
        .request(server)
        .delete("/api/issues/project")
        .send({
          _id: testId,
        })
        .end(function (err, res) {
          assert.equal(res.status, 200);
          assert.equal(res.body.result, "succesfully deleted");
          assert.equal(res.body._id, testId);
        });
      done();
    });
  });
});

This is my API:

"use strict";
const Issue = require("../model");
const mongoose = require("mongoose");
const ObjectID = require("bson-objectid");

mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
});

module.exports = function (app) {
  app
    .route("/api/issues/:project")

    .get(function (req, res) {
      const queryParams = req.query;

      Issue.find(queryParams, function (err, issues) {
        if (err) {
          console.log(err);
        } else {
          res.json(issues);
        }
      });
    })

    .post(function (req, res) {
      const project = req.body;

      if (!project.issue_title || !project.issue_text || !project.created_by) {
        res.json({ error: "required field(s) missing" });
        return;
      }
      console.log("creating new issue");

      const userId = ObjectID();
      const newIssue = new Issue({
        issue_title: project.issue_title,
        issue_text: project.issue_text,
        created_by: project.created_by,
        assigned_to: project.assigned_to,
        status_text: project.status_text,
        _id: userId,
      });

      res.send(newIssue);

      newIssue.save(function (err, user) {
        if (err) return console.log(err);
        console.log("Issue created succesfully");
      });
    })
    .put(function (req, res) {
      if (!req.body._id) {
        return res.json({
          error: "missing _id",
        });
      }
      console.log("updating issue");
      if (
        !req.body.issue_title &&
        !req.body.issue_text &&
        !req.body.created_by &&
        !req.body.assigned_to &&
        !req.body.status_text &&
        !req.body.open
      ) {
        return res.json({
          error: "no update field(s) sent",
        });
      } else {
        const fieldsToUpdate = {};
        Object.keys(req.body).map((field) => {
          if (field !== "_id" && req.body[field] !== "") {
            fieldsToUpdate[field] = req.body[field];
            fieldsToUpdate.updated_on = new Date();
          }
        });
        const id = { _id: req.body._id };
        Issue.findOneAndUpdate(id, fieldsToUpdate, function (err, issue) {
          if (err) {
            return res.json({
              error: "could not update",
              _id: req.body._id,
            });
          } else {
            if (issue === null) {
              return res.json({
                error: "could not update",
                _id: req.body._id,
              });
            } else {
              return res.json({
                result: "succesfully updated",
                _id: id._id,
              });
            }
          }
        });
      }
    })

    .delete(function (req, res) {
      console.log("deleting issue");
      const id = { _id: req.body._id };
      Issue.deleteOne(id, function (err) {
        if (err) {
          return res.json({
            error: "could not delete",
            _id: id._id,
          });
        } else {
          return res.json({
            result: "succesfully deleted",
            _id: id._id,
          });
        }
      });
    });
};

Hello!

Can you modify your code so every done() call is inside the end function in your tests? It is important to call it inside the end function because it’s asynchronous and can lead to unexpected results.

Let us know if it works.

1 Like

Thank you so much, @skaparate!

The funny thing is already tried because I saw that sometimes the done() goes inside the .end() and sometimes outside. But I think I messed up putting this inside a forEach or misplaced it somwhere. So I just discarded that option as I didn´t know what I was doing.

But I tried again this time and now it works fine. It does not crash anymore :slight_smile:

Do you happen to know when it goes inside or outside? Is something that is not clear to me… To be honest I was having a look on another codes and I don´t even know if it´s a good or bad practice…

1 Like

Whenever you call an asynchronous function, you need a way to tell the testing library/framework that the test finished, that’s what the done function is there for.

In this case, the .end callback:

.end(
function(error, response) { // this function
})

is asynchronous. If you don’t call done, then chai won’t know that the test has finished.