JavaScript testing #15. Interpreting the code coverage metric

JavaScript Testing

This entry is part 15 of 18 in the JavaScript testing tutorial

With code coverage, we can measure the percentage of our code that runs with our test. In this article, we learn how to measure our coverage and discuss whether it is a metric worth considering. We will understand its benefits and limitations and see how a high code coverage can mislead us into thinking that we tested our code thoroughly. Thanks to that, we will be better equipped to use code coverage effectively as part of our testing process.

Measuring the code coverage

Let’s create a straightforward function that will help us illustrate how the code coverage is measured.

getAgeGroup.ts

Above, we return a different age group based on the provided number. If the number is negative, we throw an error.

Let’s write a simple test using Jest.

getAgeGroup.test.ts

Our test ensures that if we provide a number smaller than 13, the function returns the child age group.

PASS src/getAgeGroup.test.ts
When the getAgeGroup function is called
and a number smaller than 13 is provided
✓ should return the Child age group

Pointing to the correct files

We should tell Jest which files to collect coverage from using the property.

jest.config.ts

If we don’t do that, Jest will collect the coverage only from the tested files. If we had a file with no tests, it would not have affected the coverage number. We should avoid that.

Running the correct command

To measure the code coverage, we must run Jest with the flag.

package.json

Let’s run the command and inspect the output.

Explaining each coverage metric

In the output above, we can see various coverage metrics. Let’s go through all of them.

Statement coverage

Our test has a 69.23 percent statement coverage. It means that 30.77 percent of executable statements remain untested, potentially hiding some bugs.

An executable statement is a statement that performs an action, such as assigning a value to a variable or calling a function.

Branch coverage

In our code, there are places where decisions are made, for example, with if statements.

Our code is branching out. Only in some cases does it reach the part. Branch coverage measures the percentage of executed branches such as the one above. Since our branch coverage is 60 percent, 40 percent of branches are not covered by our test.

Function coverage

Function coverage determines the percentage of functions called during testing. It’s important to notice that even if we don’t test the function explicitly, we call it in the function. Thanks to that, our function coverage is 100 percent. However, that does not mean that each function is thoroughly tested. This metric simply shows us that all functions in a particular file have been called.

Line coverage

The line coverage shows the number of lines that have been executed. Our line coverage is 69.23 percent, meaning 30.77 percent of lines are untested.

This metric is very similar to the statement coverage metric. In our code, we put one statement per line, so those metrics are identical in our case. However, it could be different if we would put more than one statement per line, such as the following:

Uncovered lines

This part of the coverage output tells us which lines of code are not covered by the tests. This makes it easier to pinpoint the exact places we should focus on.

Improving our test to increase the coverage

Let’s modify our test suite to cover all of the possible cases.

getAgeGroup.test.ts

PASS src/getAgeGroup.test.ts
When the getAgeGroup function is called
and a negative number is provided
✓ should throw an error
and a number smaller than 13 is provided
✓ should return the Child age group
when a number between 13 and 18 is provided
✓ should return the Teenager age group
when a number bigger than 18 is provided
✓ should return the Adult age group

Our code coverage is now 100% across all metrics, thanks to covering all cases.

The flaws of code coverage metrics

Low code coverage can be a good indicator that our tests are lacking. However, even 100 percent coverage does not guarantee a function is thoroughly tested.

getUserAgeFromJson.ts

We only need a straightforward test to achieve 100 percent coverage for the above function.

getUserAgeFromJson.test.ts

Our test achieves full code coverage, but we are far from testing every case. For example, if we provide a string that is not a valid JSON, our function throws an error. If we provide a valid JSON that does not contain the property, our function returns .

Summary

The code coverage is a good way to track how much of our code is executed by our tests. However, it does not tell us much about the quality of our tests. Even if we have the full code coverage, our tests might still miss important cases that we should cover.

Striving to have high code coverage can benefit our application, but it does not yet mean our code is bug-free. Therefore, we must also focus on creating meaningful tests that challenge our code in various scenarios to ensure the reliability of our codebase.

Series Navigation<< JavaScript testing #14. Mocking WebSockets using the mock-socket libraryJavaScript testing #16. Snapshot testing with React, Jest, and Vitest >>
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments