How can I add test coverage to an existing Node.js app?
There are a lot of different ways to do this, so if you prefer other libraries or have different testing strategies, go ahead and share them in the comments below.
I want to start by adding tests to this extractFilePath function. It's a helper function that basically takes a filename or uses index.html as a default and returns the absolute path for that file.
The first tool we'll add to our tests is mocha. Mocha is a test runner that provides structure and conveniences to our tests, as well as test reporting. And we'll add it as a dev dependency since our application code doesn't rely on it.
The main structural components mocha gives us access to are 'describe' and 'it'. These functions organize our tests. It blocks are the actual tests with assertions, and the describe blocks organize the tests. They'll contain multiple it blocks or additional describe blocks.
Replace test file
The first thing you might notice is the linter does not like these unexpected values. We can tell eslint that we're using mocha by adding it to the eslintrc file. I could add it to the root eslintrc, but eslint lets you use multiple config files, so we can make a new one in the test directory that extends the same rules and allows us to add new ones.
Now that the linter is happy, let's move our tests into mocha's structure. We can copy each test into its own it block and add a descriptive label explaining what the test is checking. We can add a description to the describe block too that will be visible in our test report.
So now we can run mocha. Without any arguments, it will run all files with the .js extension in the test directory. And two passing tests. Great, right? Let's double check this is working as expected by forcing a test to fail. Still 2 passing tests. Why? Mocha will treat every it block as a success unless an error is thrown in the callback. So, if we change console outputs to errors, we get the failures we expect.
The next area of our tests that we'll clean up are the assertions themselves. Instead of manually laying out this logic for every test, we can use an assertion library to create much more readable assertions with better error output.
We'll use chai as the assertions library, and we'll also use a chai extension, chai-string, that adds assertions specifically for testing strings.
Now we can import them into our test, and tell Chai to use Chai String. Chai comes with 2 assertions libraries, expect and should. They do basically the same thing but use different syntax for assertions. I prefer expect, but you could use should to do the same thing.
Now we just need to replace our handmade assertions with chai's.
We get the same output when we run the tests, and if we make one fail … we get a lot more information about why the test failed. Here we can see the expected and actual outcomes.
This test file works great. Now let's add some conveniences to the test suite that will make it easier to grow.
First, let's make sure mocha is only looking for tests in the files we want it to. By default, mocha will load all files in the test directory with a `.js` extension. This works for now, but we can take more control over which files and scripts mocha loads. This argument will tell mocha to look for tests in the test directory, as well as all descendant directories, and only in files that end with a .spec.js extension.
It would be tedious to type this in every time we wanted to run our tests, so let's replace this default test script with something more useful. Now we only need to run yarn test to run our test suite with all the options we need.
Add test-loader file to make global for chai.expect
Next, let's create a test loader file that mocha will load before it runs any tests. This is the perfect place to load and configure other testing libraries like chai. In this new file we'll load chai and the chai string extension in the same way as we do in our test. The only difference is we want to create a global expect variable that we can use throughout our tests.
Now when we remove the require statements from our existing test, everything works, but the linter is unhappy. We'll need to tell eslint about chai like we told it about mocha. While there is a mocha env option that informs chai about mocha's describe and it functions, there is not an equivalent chai environment. Instead, we can be explicit about the new expect variable eslint should ignore.
Finally, we can tell mocha to load this file using the --require option.
Now this test and any future test files we create can use expect assertions, including the chai string extension, without any importing or configuration. We'll be able to load other libraries here as well.
We've set up the foundation for all the tests we'll write in the future.
- Mocha will organize, run and give feedback on our tests.
- Chai lets us write expressive test assertions
- Test loader file saves us from repeatedly importing the testing libraries we use.
Still, there's a lot of room for improvement. This console output from ChattrBox, for example, isn't ideal. In the next screencast, we'll use a stub to suppress that output, and a spy to get better insight into the internals of the functions we test.