Thanks to our friend the second law of thermodynamics, every aspect of our lives tends to turn into chaos if not subject to proper maintenance and dedication. Software projects are not an exception and I do not need to tell you how easy it is for a software product to evolve into an awful buggy mess. Fortunately, nowadays we have a set of validated practices that help and tell us how to create, design and maintain quality software.
As developers, the most common scenario for us is writing unit tests or integration tests. Ocassionally, you might find yourself writing other types of tests, such as functional or E2E tests. I will mainly focus on unit testing here.
Among JavasScript testing frameworks, I’ve happily used Mocha for years. It is simple, fast, stable and widely adopted. Mocha allows us to easily create test suites (not necessarily unit tests, but also integration tests, and even functional tests). This simplicity comes with a small disadvantage: if your need other testing features, you’ll have to spend more time installing and configuring additional tools.
I recently gave Jest a try. It feels a bit slower than Mocha, but I like the fact that many features are already integrated and the setup is a piece of cake. For example, I am a big fan of desktop notifications while developing. Sometimes the terminal where you run tests is minified, or not visible in your desktop at the exact moment you make a change in your code. With desktop notifications, you’ll always have instant visual feedback if you break something, which is specially helpful for TDD and its red-green-refactor cycle. However, if you want desktop notifications with Mocha, you’ll need to install additional libraries, which differ across plaforms (Linux, Windows and Mac).
Jest also includes built-in code coverage reports, so, for example, you can control whether or not your CI builds pass based on these reports. When working with Mocha, you would need to install coverage tools, such as Istanbul. That being said, Jest’s built-in code coverage is based on Istanbul underneath, so the only difference is that Jest installs it for you and saves you the hassle of writing some scripts for code-coverage checking.
In addition to using Mocha or Jest, I also use Chai as a way to write more readable assertions. The more readable your tests are, the better. Why? Keeping your tests readable makes them easier to maintain. If your tests are easy to maintain, they are less likely to be abandoned.
Additionaly, we can combine ESLint with plugins, such as Prettier, to autofix our linting errors.
So, now that we have our testing and linting tools installed and configured, we need to have a set of task or script definitions that integrates these tools with our development and build cycles. Basically this means creating a set of scripts necessary for all your development and build tasks. While Gulp or Grunt are certainly an option, I also think they add unnecessary abstractions and introduce more dependencies in your project. My preference here is to use NPM scripts to end up with something like this in our package.json:
"start": "node src",
"start:watch": "nodemon src",
"test": "jest test --collectCoverage --ci",
"test:watch": "jest test --watchAll --notify",
"lint": "eslint .",
"lint:fix": "eslint --fix ."
What did we achieve here? Well, now, we have all these commands at our disposal, so for example, when I start making changes to my code, I will probably run
npm run start:watch to launch my app in development mode, and, in another terminal,
npm run test:watch to run my continuous testing script so that tests are run every time I make a change to my source code.
Our CI environment should also benefit from these scripts and base build definitions on them. A hypothetic testing stage would execute something like this:
npm run lint
Which lead us to my last point for today…
Be sure to run your tests and linter in your CI builds. This will ensure that your tests and your coding standards are always in use.
If you do not have an automated continous integration system, your tests and coding standards are doomed. My experience is that developers won’t run tests every time they make a change(for hundreds of reasons: unawareness, lazyness, bad memory, lack of time…), your tests and coding standards will be eventually abandoned, and all your previous efforts will have been a waste of time.
- Unit testing with Jest and Chai.
- Test watcher for continous testing.
- Test coverage checking.
- ES6 linting based on ESlint and Prettier.
You can go and take a look at the code or also install the tool via NPM to generate your own projects.
In the next post, I’ll talk more about CI and release management.