Testing

Software Testing is necessary because we all make mistakes. Some of those mistakes are unimportant, but some of them are expensive or dangerous. We need to check everything and anything we produce because things can always go wrong – humans make mistakes all the time.

Ideally, someone else should check our work because another person is more likely to spot the flaws.

Automatic tests help to find some of the problems but the tests will never be perfect and couldn't guaranty error free code. How and then to make this tests can be different:

  • Write the tests first and then code till everything succeeds.
  • Write your code and then the tests to ensure it will also keep working for the future.
  • Write tests for each bug, then fix it till the test succeeds.
  • Neither write tests.

The tests itself may be unit tests which check some internal part of the software or end-to-end test which checks whole procedures.

1. Mocha

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases.

1.1. Helper

As mocha itself needs a powerful assertion library to check within the tests Should.js allows just this. It supports a lot of methods which can be used to check any object. Another possibility is Chai which allows also to use an expect style which is more consistent as should.

Here in Alinex mostly Chai with expect style will be used.

1.2. Installlation

$ npm install mocha chai --save-dev

This will install the modules. The tests itself are mostly stored in the folder test/mocha.

{
  "scripts": {
    "test": "mocha --require babel-core/register --require babel-polyfill test/mocha"
  }
}

Now you can run your tests using:

$ npm run test

1.3. Writing tests

An example test may look like:

import chai from 'chai'
import request from 'request'

import server from '../../src/server'

const expect = chai.expect

// start server before tests
before((cb) => {
  server.init({ logging: null }) // no request logging needed
  server.start()
  .then(cb)
})

// stop server after tests
after(() => {
  server.stop()
})

// run the tests
describe('server', () => {

  it('should have default folders', async () => {
    const compiler = new Compiler({schemaPath: 'test/data/config'})
    const files = await compiler.schema()
    expect(files, 'config.files').to.have.lengthOf(1)
  })

})

The test itself starts with the expect method which should be given the object to test and the information about what is tested. This makes the error messages readable. after that a concatenation of helpful words and assertion definitions will follow (some as functions):

expect(files, 'config.files').to.have.lengthOf(1)

1.4. Plugins

There are lots of Plugin for chai to use but only some of them which are decided as very useful are shown here.

Promises

This is done using the extension chai-as-promised which was also included in the above installation and examples.

Now you may also use the following tests:

import chaiAsPromised from 'chai-as-promised'
chai.use(chaiAsPromised)

expect(promiseFn()).to.be.fulfilled
expect(promiseFn()).to.eventually.deep.equal("foo")
expect(promiseFn()).to.become("foo") // same as `.eventually.deep.equal`
expect(promiseFn()).to.be.rejected
expect(promiseFn()).to.be.rejectedWith(Error) // other variants of Chai's `throw` assertion work too

Http

chai-http includes a request method which easily checks the result:

import chaiHttp from 'chai-http'
chai.use(chaiHttp)

chai.request('http://localhost:8080')
  .get('/')

File system

chai-fs will allow extensive test on the NodeJS filesystem. It has a lot of methods from which only some are shown here:

import chaiFs from 'chai-fs'
chai.use(chaiFs)

expect(path).to.be.a.directory().with.files(array)
expect(path).to.be.a.directory().and.empty
expect(path).to.be.a.file().with.json

RegExp matcher

chai-match will check assertions using regular expressions:

import chaiMatch from 'chai-match'
chai.use(chaiMatch)

expect('some thing to test').to.match(/some (\w+) to test/).and.capture(0).equals('thing')

2. ESLint

To use ESLint together with Mocha and ES6 you have to add some plugins:

$ npm install eslint eslint-plugin-import eslint-plugin-promise eslint-plugin-standard --save-dev
$ npm install npm eslint-config-airbnb eslint-plugin-jsx-a11y eslint-plugin-react --save-dev
$ npm install eslint-plugin-mocha-only @mocha/eslint-config-mocha --save-dev
$ npm install babel-eslint eslint-plugin-flowtype --save-dev

In the root folder the concrete rules can be specified .eslintrc.js:

module.exports = {
  env: { es6: true, node: true },
  extends: 'airbnb',
  parser: "babel-eslint",
  parserOptions: { sourceType: 'module' },
  rules: {
    'indent': [ 'error', 2 ],
    'linebreak-style': [ 'error', 'unix' ],
    'quotes': [ 'error', 'single' ],
    'semi': [ 'warn', 'never' ],
    'no-unused-vars': [ 'warn' ],
    'no-console': [ process.env.NODE_ENV === 'production' ? 'error' : 'warn' ]
  }
};

The babel-eslint parser is necessary to also parse the flow annotations correctly.

And extend the mocha template for your test folder by placing an test/mocha/.eslintrc.js file in your test folder:

module.exports = {
  extends: [ 'mocha/es6' ],
  rules: {
    quotes: 'off',
    no-unused-vars: 'off',
    no-console: 'off'
  }
};

If you want some explanation for a rule best way ist too look up Google using the message or the rule name combined with eslint. Or look directly in the rules index.

3. Monitoring Changes

Nodemon is a utility that will monitor for any changes in the source and automatically restart the code. Perfect for development.

Installation

$ npm install nodemon --save-dev

Now nodemon can be used instead of node to run the code and it will always stop and restart the code if the code changes. It can also be used to start any other executable.

Automatic tests

The following package.json setup will rerun the linter and unit tests each time you save changes. It has to be called using npm run dev. This is optimal for modules development.

{
  "scripts": {
    "dev": "nodemon src/index.js --exec 'npm run lint -s && npm run test'",
    "lint": "eslint src --ext .js",
    "test": "nyc --require babel-core/register --require babel-polyfill mocha test/mocha"    
  }
}

Server restart

To work with server we mostly only lint and then start the application itself. Therefore set the following package.json and also run it using npm run dev:

{
  "scripts": {
    "dev": "nodemon src/start.js --exec 'npm run lint; babel-node --require babel-polyfill --exec node_modules/.bin/babel-node'",
    "lint": "eslint src --ext .js"
  }
}

results matching ""

    No results matching ""