Adding Mocha to a Project

Adding Mocha to a Project

This is more in my series in setting up a project.  I am knee-deep in setting up this project and I am trying to set up my base template at the same time.  I’ve done this a few times – it feels great to be setting up a project from scratch.  The beauty in this approach is that you can write all of your backend queries and CRUD operations before you’ve even thought about the UI and because you’ve coded your tests in parallel you know that everything works.

  1. install mocha,  and nodemon using your favorite package manager.
    yarn add mocha -D
    yarn add nodemon --dev 

    Note that both flags accomplish the same thing – the libraries are installed as dev devpendencies since we don’t need or want them in production.

  2. Create a “test” script in your package.json.  You certainly could put this under some other flag but using something like “test” will allow you to switch to a testing phase where tests will run only when you are in that mode.  It can be a little annoying to be doing work under something like “development” and watching the tests spin over and over.  This gives you the option to switch it on or off.  Nodemon looks for changes and then launches mocha.  Rinse and repeat for every change (which is why you might not want this on all of the time).  The recursive flag tells mocha to search all folders underneath “test” in the root folder.  Big  gotcha  here!   On  my  windows  pc  I could  not  have  a space  between  “test” and  “&&”  – the  space was  preserved  in  the  var.
    "test": "SET NODE_ENV=test&& nodemon --exec mocha --recursive",
  3. Issue “yarn test” to load mocha and start to execute tests.  Because we are using nodemon mocha will reload on every change.
  4. If you did set up a different test setup be sure to take care of your backend setup.  For example, in my MERN application I have three different environments (dev, staging and prod).  It seems silly to create a fourth environment.  Instead, just set your “test” environment to use dev’s credentials (see more here).
    switch (process.env.NODE_ENV) {
      case 'test':
        module.exports = require('./test');
        break;
      case 'test':
      case 'development':
        module.exports = require('./development');
        break;

     

  5. Set up your test folder structure.  In this example I want to cover all CRUD operations on server controller for the “user” model.  Obviously you could substitute in whatever test you want here.

  6. Create a “test_helper.jsf” file in the “test” folder.
    const mongoose = require('./../server/node_modules/mongoose');
    require('dotenv').config({ path: '.env.' + process.env.NODE_ENV });
    const keys = require('./../server/config/keys');
    
    
    before(done => {
      // need to write this to another database
      mongoose.Promise = global.Promise;
      try {
        // Connect to the MongoDB cluster
        mongoose.connect(
          keys.mongoURI,
          { useNewUrlParser: true, useUnifiedTopology: true },
          console.log(' Mongoose is connected'S),
        );
        mongoose.connection
          .once('open', () => done())
          .on('error', err => {
            console.warn('Warning', err);
          });
      } catch (e) {
        console.log('could not connect');
      }
    });
    
    
    beforeEach(done => {
      const { users } = mongoose.connection.collections;
      users
        .drop()
        .then(() => done())
        // catch error on first run when db does not exist
        .catch(() => done());
    });
    Note that the “before” runs before all other execution in Mocha.  In this block we get a handle to the test database.  We don’t want to use any of the other databases because we will be addling and (more importantly) deleting data.
    The “beforeEach” runs before each test.  We want to “drop” (remove) the users collection before each test is run.
  7. The contents of the “users_controller_test.js” file
    const assert = require('assert');
    const request = require('supertest');
    const mongoose = require('../../server/node_modules/mongoose');
    const app = require('../../server/index');
    const User = mongoose.model('user');
    
    
    describe('Users controller', () => {
      it('Post to /api/users creates a new user', done => {
        User.count().then(count => {
          request(app)
            .post('/api/users')
            .send({ emailAddress: 'test@test.com', nickName: 'joe' }) // sending test data
            .end(() => {
              User.count().then(newCount => {
                assert(count + 1 === newCount);
                done();
              });
            });
        });
      });
    });

    A simple mocha example.  We use the “describe” to give a top-level visual definition of the tests in this area.  Inside of this block we will have one or more “it” blocks.  For example, we would have one “it” block before each CRUD operation.    In this example we created a new user (remember that all users were cleared out before each test!) and then we simply do a count to see if the user exists.  And edit would test to see if the updates made it to the database.  A test will fail if the result of the it block is not true (a return of null will not trigger a fail).  Note that “done” is necessary because this is essentially a promise (“done” passed in as a reference).

  8. And we have a successful test:

  9. If you only want to run one particular test at a time you would phrase the it block at “it.only” instead of “it”.  The same is true of “describe” – you can use “describe.only” to run a test for a specific describe block.
  10. The edit and delete test for the user.
    it('Post to /api/users/id edits a user', done => {
      const user = new User({ emailAddress: 't@t.com', plan: 'free' });
    
    
      user.save().then(() => {
        request(app) //get request helper from supertest
          .put('/api/users/' + user._id)
          .send({ plan: 'paid' })
          .end(() => {
            //signals the end of the update
            User.findOne({ emailAddress: 't@t.com' }).then(user => {
              assert(user.plan === 'paid');
              done();
            });
          });
      });
    });
    it('DELETE to /api/users/id deletes a user', done => {
      const user = new User({ emailAddress: 't@t.com' });
    
      user.save().then(() => {
        request(app) //get request helper from supertest
          .delete(`/api/users/${user._id}`)
          .end(() => {
            //signals the end of the update
            User.findOne({ emailAddress: 't@t.com' }).then(user => {
              assert(user === null);
              done();
            });
          });
      });
Tags
,

Add a comment

*Please complete all fields correctly

Related Blogs