I’ve been working on a front-end application that uses ESLint for linting and Mocha as our testing framework. We’ve tried a few solutions to integrate linting into our workflow but it can easily be ignored. To make sure linting errors were given the attention they deserved we decided to turn linting errors into test failures.
Generating tests
ESLint provides all the tools we need to lint our files and get the results back directly in JavaScript.
To get started, create a file called called eslint-test.js
in your test
directory.
The first thing we need to do is collect an array of file paths we want to lint
and get the results from ESLint after telling it our environment information and
to use our .eslintrc
.
// eslint-test.js
import glob from 'glob';
import { CLIEngine } from 'eslint';
import { assert } from 'chai';
const paths = glob.sync('./+(app|test)/**/*.js');
const engine = new CLIEngine({
envs: ['node', 'mocha'],
useEslintrc: true,
});
const results = engine.executeOnFiles(paths).results;
Now that we have the results we can define our describe
block and generate
tests in that block. Let’s add that to the end of our eslint-test.js
file.
describe('ESLint', function() {
results.forEach((result) => generateTest(result));
});
For each result we want to extract the filePath
and messages
. filePath
will be used to generate a good test name. We’ll use messages
to check if we
have any messages and if we do, we fail the test with a well formatted reason
built from messages
.
function generateTest(result) {
const { filePath, messages } = result;
it(`validates ${filePath}`, function() {
if (messages.length > 0) {
assert.fail(false, true, formatMessages(messages));
}
});
}
Finally we can loop over each of the messages and generate a failure message telling us the line number, column number, failure message, and the ESLint rule name.
function formatMessages(messages) {
const errors = messages.map((message) => {
return `${message.line}:${message.column} ${message.message.slice(0, -1)} - ${message.ruleId}\n`;
});
return `\n${errors.join('')}`;
}
Here’s the test generator code all together:
import glob from 'glob';
import { CLIEngine } from 'eslint';
import { assert } from 'chai';
const paths = glob.sync('./+(app|test)/**/*.js');
const engine = new CLIEngine({
envs: ['node', 'mocha'],
useEslintrc: true,
});
const results = engine.executeOnFiles(paths).results;
describe('ESLint', function() {
results.forEach((result) => generateTest(result));
});
function generateTest(result) {
const { filePath, messages } = result;
it(`validates ${filePath}`, function() {
if (messages.length > 0) {
assert.fail(false, true, formatMessages(messages));
}
});
}
function formatMessages(messages) {
const errors = messages.map((message) => {
return `${message.line}:${message.column} ${message.message.slice(0, -1)} - ${message.ruleId}\n`;
});
return `\n${errors.join('')}`;
}
The results
Now that we have our ESLint test generator we can run our test suite and see it in action.
$ mocha
1) ESLint validates ./app/index.js
AssertionError:
44:35 Missing semicolon - semi
It works! Our failure tells us the failure’s file, line number, column number, and linting error!
Conclusion
This combined with GitHub’s required checks can make it much harder to merge code with poor style in your application. You can also take this same idea and apply it to other tools or linters like Hound.