Ad

Ensuring Document Is Retrieved Without Using SetTimeout() In Mocha Test

- 1 answer

I have written some tests in my Node project using Mocha and Chai. In one of my tests I create an agenda.js job, and then save it to the database. Then I retrieve that document from my MongoDB database and run some checks on it. After numerous configurations, I've found a construction for the test that works. But to get this to work I had to add a setTimeout() within the first it block, because otherwise the it checks start running before the document is retrieved from the database.

While the following construction works, I'd like to know what would be a better way of doing this. It seems to me the whole point of the before block in Mocha is to ensure that whatever work is defined within it is done BEFORE the it checks run. That doesn't seem to be happening in my case - hence the need for the setTimeout(). So how can I accomplish that without resorting to using the `setTimeout()?:

const assert = require("chai").assert;
const expect = require("chai").expect;
const chai = require("chai");
chai.use(require("chai-datetime"));
const Agenda = require('agenda');

const config = require('./../../configuration');
const url = config.get('MONGO_URL');
const dbName = config.get('MONGO_DATABASE');
const collection = config.get('MONGO_COLLECTION');
const createAgendaJob = require('./../../lib/agenda-jobs/contact-firstname-to-proper-case');

const MongoClient = require('mongodb').MongoClient;
const client = new MongoClient(url);

describe("Contact FirstName to Proper Case", async function () {
  const jobName = "Contact FirstName To Proper Case";
  const testDate = new Date(2019, 01, 01);
  let result;
  let agenda;
  this.timeout(10000);
  before(async function () {
    const connectionOpts = {
      db: {
        address: `${url}/${dbName}`,
        collection
      }
    };

    agenda = new Agenda(connectionOpts);
    await new Promise(resolve => agenda.once('ready', resolve));
    await createAgendaJob(agenda);
  });
  describe("Check Contact FirstName To ProperCase Found Job", async function () {
    let result;
    before(async function () {
      await client.connect(async function (err) {
        assert.equal(null, err);

        const db = await client.db(dbName);

        result = await db.collection("jobs").findOne({
          "name": jobName
        });

        client.close();
      });
    });
    it("should have a property 'name'", async function () {
      await new Promise(resolve => setTimeout(resolve, 1000)); // Here is the setTimout()
      expect(result).to.have.property("name");
    });
    it("should have a 'name' of 'Contact FirstName To Proper Case'", async function () {
      expect(result.name).to.equal("Contact FirstName To Proper Case");
    });
    it("should have a property 'type'", function () {
      expect(result).to.have.property("type");
    });
    it("should have a 'type' of 'normal'", function () {
      expect(result.type).to.equal("normal");
    });
    it("should have a property 'repeatTimezone'", function () {
      expect(result).to.have.property("repeatTimezone");
    });
    it("should have a property 'repeatInterval'", function () {
      expect(result).to.have.property("repeatInterval");
    });
    it("should have a property 'lastModifiedBy'", function () {
      expect(result).to.have.property("lastModifiedBy");
    });
    it("should have a property 'nextRunAt'", function () {
      expect(result).to.have.property("nextRunAt");
    });
    it("should return a date for the 'nextRunAt' property", function () {
      assert.typeOf(result.nextRunAt, "date");
    });
    it("should 'nextRunAt' to be a date after test date", function () {
      expect(result.nextRunAt).to.afterDate(testDate);
    });
  });
});
Ad

Answer

The async function inside before may be resolving early. In that case I would wrap it in a new Promise and resolve when I am sure all async code has resolved to completion.

//...
before(function () {
  return new Promise((resolve, reject) => {
    client.connect(async function (err) {
      if(err) return reject(err);
      try {
        const db = await client.db(dbName);

        result = await db.collection("jobs").findOne({
        "name": jobName
        });

        client.close();
      } catch(err){
        return reject(err);
      }
      return resolve();
    });
  });
})
//...

Alternatively, call the done callback, passing in a truthy value if there is an error.

//...
before(function (done) {
  client.connect(async function (err) {
    if(err) return done(err);
    try {
      const db = await client.db(dbName);

      result = await db.collection("jobs").findOne({
      "name": jobName
      });

      client.close();
    } catch(err){
      return done(err);
    }
    done();
  });
})
//...

Ad
source: stackoverflow.com
Ad