mochajs / mocha

☕️ simple, flexible, fun javascript test framework for node.js & the browser
https://mochajs.org
MIT License
22.63k stars 3.02k forks source link

Mocha does not run tests defined within a function #3347

Closed AbdelrahmanHafez closed 6 years ago

AbdelrahmanHafez commented 6 years ago

Prerequisites

Description

I am trying to test my API with mocha & superagent, the thing is, I need to wait for the db to open, then export the app, because the tests require some initialization code that depends on the db.

So, in order to do that, I had to wrap my test code within an async function and call it, but it doesn't run the tests.

// test.js

describe('math', async () => {
  it('multiplies', () => {
    let result = 2 * 2;
    result.should.equal(4);
  });
});

const test = async () => {
  const should = require('chai').should();
  const request = require('supertest');
  const path = require('path')
  const app = await require(path.resolve('./server'));

  describe('math', async () => {
    it('adds', () => {
      let sum = 1 + 1;
      sum.should.equal(2);
    });
  });

  describe('users', async () => {
    it('creates a customer', async () => {
      let res = await request(app).get('/api/should-404/');
      res.status.should.equal(200);
    });
  });
};

test().catch(console.err);
// server.js

const run = async () => {
  const app = express()();
  const mongoose = require('mongoose');
  await mongoose.connect('mongodb://localhost:27017/speero_test');

  const db = mongoose.connection;

  db.on('error', console.error.bind(console, 'connection error:'));
  db.once('open', () => {console.log.bind(console, 'connection :')});

  // the line below depends on the db
  const initRolesPolicies = require(path.resolve('./initialize/initPolicies'))();

  app.listen(port, function () {
    console.log('Autotash is running ' + process.env.NODE_ENV.toUpperCase() + ' on port ' + port + ' !');
  });

  return app;
}

module.exports = run().catch(console.err);

Steps to Reproduce

Expected behavior: All tests to run, the multiplication, the addition, and the customer creation.

Actual behavior: Only the multiplication test (the one outside the test function) run, the ones inside are completely ignored even though I execute the function at the bottom.

I don't completely like wrapping the test suites inside a function, but I have to await for the app to be returned, I am open for any other approaches to accomplish this. I just need to wait for db to be ready before exporting the app object and running the tests.

Reproduces how often: [100%]

Versions

ORESoftware commented 6 years ago

First thing I notice:

const test = async () => {  // not OK =>  using async function...not kosher in this case
   // ...

  describe('math', async () => {   // this is NOT OK either
    it('adds', () => {
      let sum = 1 + 1;
      sum.should.equal(2);
    });
  });

 // ....
};

you shouldn't register any of the following asynchronously:

it/describe/before/after/afterEach/beforeEach, etc.

all hooks should be registered synchronously, otherwise you will get unknown/undefined behavior.

AbdelrahmanHafez commented 6 years ago

What if I need to do some async functions on before and beforeEach, like cleaning the database for example?

How should I handle that?

Edit: I misunderstood. I should write the describe(...) outside an asynchronous function, however, what I write inside the describe(...) can be async.

AbdelrahmanHafez commented 6 years ago

Actually, I think I just found a solution!

// test.js

const should = require('chai').should();
const request = require('supertest');
const path = require('path')
let app;

before(async () => {
  try {
    app = await require(path.resolve('./server'));
  }
  catch (err) {
    console.log(err);
  }
});

describe('math', async () => {
  it('multiplies', () => {
    let result = 2 * 2;
    result.should.equal(4);
  });
  it('adds', () => {
    let sum = 1 + 1;
    sum.should.equal(2);
  })
});

describe('users', async () => {
  it('creates a customer', async () => {
    let res = await request(app).get('/api/should-404/');
    res.status.should.equal(200);
  });
});

This did the trick after two days of trying to figure it out.

I am completely new to testing, is there a better way to do this?

ORESoftware commented 6 years ago

yep what you have now is good, that is a very common pattern with Mocha. Note that I made a correction to my previous statement. Let me explain better:

// this is bad
describe('foo', async _ => {
    it('bar', async done => {
    });
});
// this is good
describe('foo',  _ => {   // remove async from here
    it('bar', async done => {
    });
});
AbdelrahmanHafez commented 6 years ago

Thanks a lot!

LordOfTheDings commented 2 years ago

Thank you very much for posting your solution. I had the same problem and this provided me with the necessary information.

AbdelrahmanHafez commented 2 years ago

@LordOfTheDings Nothing makes me happier than coming back to things years earlier that saved someone else time. Pay it forward 👍

RishabhAtomos commented 7 months ago

describe('Test', () => { console.log("describe1") it('should return 200 status code', function (done) { console.log("IT") chai.request(app) .get('/v1/cis/test') .end((error, response) => { expect(response).to.have.status(200); }); done(); }); console.log("describe2") });

Why my it() is not getting executed?

AbdelrahmanHafez commented 7 months ago

@RishabhAtomos Your code looks right, it should run but the test doesn't really test anything because you are calling done() too quickly. Your code calls done() before the .end(...) happens

  chai.request(app)
      .get('/v1/cis/test')
      .end((error, response) => {
        expect(response).to.have.status(200);
      });
      done();

You need to move the .done() within the .end(...) callback.

  chai.request(app)
      .get('/v1/cis/test')
      .end((error, response) => {
        expect(response).to.have.status(200);
        done();
      });

That way your test exits execution only after there is a response from the chai.request().get().

Worth noting that callbacks and done() are things of the past, I highly recommend you learn more about promises and async/await, it'll make your life much easier.