Since the new year, I've been doing a lot more JavaScript development and I'm catching up on testing practices. Things are slightly different from the Python and Java world I've primarily lived in for the last 12 years: some simpler, some harder. As I set up a new Next.js project, I figure I'd document the base configuration to allow me to run Unit tests and Integration tests independently for Test Driven Design (TDD) and together for CI/CD pipelines. I will try to write additional guides for various test scenarios in the future. However, despite my best intentions, I seldom get around to "part 2" articles it seems. Hit me up on twitter if you would like more of these.
Note: It is out of the scope of this post to talk about specific approaches with Jest, nor concepts like mocking, or other types of testing such as contract or functional testing.
By default, jest looks for a configuration file named jest.config.js in your project root. We can also override config directives by passing arguments to our scripts. We will do both, but first, lets set up the config file.
// Jest Config - place in ./jest.config.js
module.exports = {
clearMocks: true,
// Don't look for tests in these directories
testPathIgnorePatterns: ['<rootDir>/build/', '<rootDir>/node_modules/'],
// Define where to output the coverage report
coverageDirectory: '<rootDir>/coverage',
// Define what to include in the coverage report
collectCoverageFrom: [
// Collect Coverage from:
'**/*.js', // All Javascript files
'!**/node_modules/**', // ... except node modules
'!**/build/**', // ... and Next.js build dir
'!**/coverage/**', // ... and the coverage dir itself,
'!**/*.config.js' // ... nor any config files (eg. next.config.js nor jest.config.js)
]
};
Notes:
Create a file named cool.integration.test.js and cool.test.js. You can put these files anywhere in your folder structure as long as they're not skipped above in the config. I'm not getting into testing techniques here, so these simple tests will do.
// cool.test.js
/* eslint-env jest */
describe('Ultra Fast Unit test', () => {
test('should pass when run', () => {
expect(1).toBe(1); // Change the later 1 to 2 to ensure test is running (by failing)
});
});
and
// cool.integration.test.js
/* eslint-env jest */
describe('Slow Integration test', () => {
test('should pass when run', () => {
expect(1).toBe(1); // Change the later 1 to 2 to ensure test is running (by failing)
});
});
Notes:
Next we will run our tests - first just the unit tests, then just the integration tests, and finally all the tests.
Add the following bold lines to your package.json in the scripts section:
...
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js",
"lint": "eslint .",
"test": "jest --collectCoverage true",
"unit": "jest --testRegex '(?<!integration\\.)test\\.js$'",
"integration": "jest --testRegex 'integration\\.test\\.js$'",
}
...
Run the Unit Test
In your terminal, run npm run unit or yarn run unit
If all goes well, you should see output that includes: 1 passed, 1 total.
Go ahead and change your test to expect 1 to be 2. Re-run your tests and watch them fail to see how that looks.
Run the Integration Test
In your terminal, run npm run integration or yarn run integration
If all goes well, you should see output that includes: 1 passed, 1 total.
Go ahead and change your test to expect 1 to be 2. Re-run your tests and watch them fail to see how that looks.
Run All the Tests and Generate Coverage
In your terminal, run npm run test or yarn run test
If all goes well, you should see output that includes: 2 passed, 2 total.
You will also see a coverage report that looks like this:
Notes:
In the previous step, a mini coverage report was printed to the screen when running the full test suite. However, in your root directory a new folder was generated called coverage. Open the index.html file in this directory for a more interactive report. Here's a handy article on how to get the most out of this report.
While 100% coverage is unreasonable to attain, you should be shooting for an overall target range somewhere between 60% to 85%. If you drop below the lower end of the range, it probably means your code is increasingly unstable overtime. Personally, I try to live by the idea that if I change some code I didn't originally write and a test doesn't break, then I have no idea what the code was supposed to do nor if it continues to do it. Sure manual testing might catch a drastic change in functionality, but it is poor at catching edge cases or changes that break unrelated functionality that otherwise used to work. More importantly, the coverage report now tells you which lines of code do not have tests around them. This is a good time to determine if some absolutely critical business logic is covered by tests or not. Who cares about commonly used display logic, if you have code that can lead to customer data loss, catastrophic failure, or just must absolutely work - this report will give you confidence (or lack thereof) in your code.
If you made it through the above steps, you should be set for writing stable testable code.
Want to learn more? Check out this jest cheat sheet, React Snapshot testing, Sinon for gooder mocking, and finally, read this good article about testing at AirBnB, which has some really good dev practices.
Found a typo? Am I wrong? Hit me up on twitter.