Niklas Higi
Niklas Higi

Reputation: 2309

Mocha ignores some tests although they should be run

I'm correctly working on refactoring my clone of the express-decorator NPM package. This includes refactoring the unit tests that were previously done using AVA. I decided to rewrite them using Mocha and Chai because I like the way they define tests a lot more.

So, what is my issue? Take a look at this code (I broke it down to illustrate the problem):

test('express', (t) => {
  @web.basePath('/test')
  class Test {

    @web.get('/foo/:id')
    foo(request, response) {
      /* The test in question. */
      t.is(parseInt(request.params.id), 5);
      response.send();
    }

  }

  let app = express();
  let controller = new Test();
  web.register(app, controller);
  t.plan(1);

  return supertest(app)
    .get('/test/foo/5')
    .expect(200);
});

This code works.


Here's (basically) the same code, now using Mocha and Chai and multiple tests:

describe('The test express server', () => {
  @web.basePath('/test')
  class Test {

    @web.get('/foo/:id')
    foo(request, response) {
      /* The test in question. */
      it('should pass TEST #1',
        () => expect(toInteger(request.params.id)).to.equal(5))
      response.send()
    }
  }

  const app = express()
  const controller = new Test()
  web.register(app, controller)

  it('should pass TEST #2', (done) => {
    return chai.request(app)
      .get('/test/foo/5')
      .end((err, res) => {
        expect(err).to.be.null
        expect(res).to.have.status(200)
        done()
      })
  })
})

The problem is that the TEST #1 is ignored by Mocha although that part of the code is run during the tests. I tried to console.log something there and it appeared in the Mocha log where I expected it to appear.

So how do I get that test to work? My idea would be to somehow pass down the context (the test suite) to the it function, but that's not possible with Mocha, or is it?

Upvotes: 1

Views: 662

Answers (1)

Louis
Louis

Reputation: 151380

It looks like you are moving from tape or some similar test runner to Mocha. You're going to need to significantly change your approach because Mocha works significantly differently.

tape and similar runners don't need to know ahead of time what tests exist in the suite. They discover tests as they go along executing your test code, and a test can contain another test. Mocha on the other hand requires that the entire suite be discoverable before running any test.* It needs to know each and every test that will exist in your suite. It has some disadvantages in that you cannot add tests while the Mocha is running the test. You could not have a before hook for instance do a query from a database and from that create tests. You'd have instead to perform the query before the suite has started. However, this way of doing things also has some advantages. You can use the --grep option to select only a subset of tests and Mocha will do it without any trouble. You can also use it.only to select a single test without trouble. Last I checked, tape and its siblings have trouble doing this.

So the reason your Mocha code is not working is because you are creating a test after Mocha has started running the tests. Mocha won't right out crash on you but when you do this, the behavior you get is undefined. I've seen cases where Mocha would ignore the new test, and I've seen cases where it executed it in an unexpected order.

If this were my test what I'd do is:

  1. Remove the call to it from foo.

  2. Modify foo to simply record the request parameters I care about on the controller instance.

    foo(request, response) {
      // Remember to initialize this.requests in the constructor...
      this.requests.push(request);
      response.send()
    }
    
  3. Have the test it("should pass test #2" check the requests recorded on the controller:

    it('should pass TEST #2', (done) => {
      return chai.request(app)
        .get('/test/foo/5')
        .end((err, res) => {
          expect(err).to.be.null
          expect(res).to.have.status(200)
          expect(controler.requests).to.have.lengthOf(1);
          // etc...
          done()
        })
    })
    
  4. And would use a beforeEach hook to reset the controller between tests so that tests are isolated.

Upvotes: 2

Related Questions