Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Istanbul / tap code coverage report a switch statement as not covered when each conditional path is covered?

In a node.js application I have a class with a getter that contains a large switch statement, same as this but larger with unreleased-product-specific values instead of a, b, c, 1, 2, 3 etc:

Class SomeClass () {
  constructor (someVar) {
    this.someProp = someVar
  }
  get someGetter () {
    switch (this.someProp) {
      case 'a':
      case 'b':
        return 1

      case 'c':
      case 'd':
      case 'e':
        return 2

      case 'f':
        return 3

      case undefined:
        return null

      default:
        return 0
    }
  }
}

I then have a test case structured like this, that reaches each of the possible paths:

const expectedResults = new Map(Object.entries({
  a: 1,
  c: 2,
  f: 3,
  null: null,
  z: 0
}))

for (const [testCase, expected] of expectedResults) {
  const someInstance = new SomeClass(testCase)
  t.equal(someInstance.someGetter, expected)
}

t.end()

This gave me coverage on all of the possible paths and no red flags in the node tap coverage report.

Hover, I get one (yellow) uncovered line, which is the line of the switch statement itself: line 6 in the example code, the line:

switch (this.someProp) {

I've tried adding redundant breaks after each return, but these are then (correctly!) flagged as uncovered (because they're unreachable).

I thought maybe it's complaining because it has a test for, for example, 'a' in the path that returns 1 but not 'b' in the same path. But if that's the problem I don't understand why it's flagging the switch statement itself, not those specific lines.

like image 415
user56reinstatemonica8 Avatar asked Oct 26 '25 06:10

user56reinstatemonica8


1 Answers

The uncovered line notice disappeared when I had the tests cover every possible case:, not just every path.

Since there were many, many cases in my real world code, and all of the return paths are tested as being correct, rather than extending the expectedResults map massively, I created another, simpler test condition which took a simple array of every case and simply made sure that each one was recognised (wasn't falling back to the default value). Like:

const allCases = ['a', 'b', 'c', 'd', 'e', 'f']

for (const testCase of allCases) {
  const someInstance = new SomeClass(testCase)
  t.notEqual(someInstance.someGetter, 0)
}

By comparing against the default fallback value, there's at least some value to this test, it's not purely there to meet 100% coverage: it makes sure no possible cases in the switch get deleted in future etc (e.g. if the switch statement is reordered or modified).

like image 75
user56reinstatemonica8 Avatar answered Oct 27 '25 19:10

user56reinstatemonica8