Search code examples
typescriptunit-testingjestjsnestjssonarqube

Test coverage: import statements not covered


I'm writing tests with Jest for a Nest application in typescript. The problem is, I have uncovered import statements saying else path not taken" and "branch not covered". The uncovered imports vary across classes. Sometimes it is underlined seemingly random in the middle of a line, see screenshot below. As a result, overall branch coverage is 47% only and one of the import lines is not covered.

When generating SonarQube reports that use different reporter, the issue persists.

Problem example 1

Sometimes it applies even to annotations.

Problem example 2

I already tried:

  1. To switch sourceMap = true as outlined here.
  2. Removing transform and adding moduleDirectories to package.json as described here.
  3. Moving Jest rootDir into project root.

Nothing helped. I'm totally stuck with this issue, particularly because it's a default setup used across many projects. It looks like something is of with one of the config files. Any idea what's wrong and how to fix those uncovered imports?

Here are some project configs:

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": false,
    "outDir": "./dist",
    "baseUrl": "./src",
    "incremental": false,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "noImplicitAny": true,
    "allowUnreachableCode": false,
    "strict": true,
    "alwaysStrict": true,
    "strictPropertyInitialization": false,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}

jest.config.ts

/** @format */

module.exports = {
  moduleFileExtensions: ['js', 'json', 'ts'],
  testRegex: '.*\\.spec\\.ts$',
  transform: {
    '^.+\\.(t|j)s$': 'ts-jest',
  },
  collectCoverageFrom: [
    '**/*.(t|j)s',
    '!generated/openapi/model/*',
    '!types/*',
    '!generate-typings.ts',
  ],
  rootDir: './src',
  coverageDirectory: '../coverage',
  testEnvironment: 'node',
  reporters: ['default', 'jest-junit'],
  setupFilesAfterEnv: ['../test/jest.setup.redis-mock.ts'],
};

package.json

{
  "name": "",
  "version": "0.0.0-local",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "postinstall": "npm run generate",
    "prebuild": "npm run clearDist",
    "clearDist": "rimraf ./dist",
    "generate": "rimraf ./src/generated && npm run generate:graphql && npm run generate:openapi",
    "generate:graphql": "mkdirp ./src/generated && ts-node ./src/generate-typings.ts",
    "generate:openapi": "mkdirp ./src/generated && openapi-generator-cli generate",
    "build": "nest build",
    "build:prod": "nest build --webpack",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "cross-env GRAPHQL_PLAYGROUND=true cross-env NODE_ENV=development nest start --watch",
    "start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
    "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest --runInBand",
    "test:ci": "jest --coverage --runInBand",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json",
    "test:sonar": "jest --coverage --runInBand --testResultsProcessor jest-sonar-reporter"
  },
  "dependencies": {
    "@apollo/gateway": "^0.50.1",
    "@azure/identity": "^2.0.4",
    "@azure/keyvault-secrets": "^4.4.0",
    "@nestjs/apollo": "^10.0.9",
    "@nestjs/axios": "^0.0.7",
    "@nestjs/common": "^8.4.4",
    "@nestjs/config": "^2.0.0",
    "@nestjs/core": "^8.4.4",
    "@nestjs/graphql": "^10.0.9",
    "@nestjs/passport": "^8.2.1",
    "@nestjs/platform-express": "^8.4.4",
    "@nestjs/schedule": "^1.1.0",
    "@nestjs/typeorm": "^8.0.3",
    "apollo-server-express": "^3.6.7",
    "applicationinsights": "^2.3.1",
    "axios": "^0.26.1",
    "cache-manager": "^3.6.1",
    "cache-manager-redis-store": "^2.0.0",
    "clone": "^2.1.2",
    "graphql": "^16.3.0",
    "graphql-fields-list": "^2.2.4",
    "mysql2": "^2.3.3",
    "openid-client": "^5.1.5",
    "redis": "^3.1.1",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.5.5",
    "ts-morph": "^14.0.0",
    "typeorm": "^0.2.34"
  },
  "jest-junit": {
    "outputDirectory": "./junit-reports"
  },
  "devDependencies": {
    "@nestjs/cli": "^8.2.5",
    "@nestjs/schematics": "^8.0.10",
    "@nestjs/testing": "^8.4.4",
    "@openapitools/openapi-generator-cli": "^2.4.26",
    "@types/cache-manager": "^3.4.3",
    "@types/cache-manager-redis-store": "^2.0.1",
    "@types/clone": "^2.1.1",
    "@types/cron": "^1.7.3",
    "@types/express": "^4.17.13",
    "@types/jest": "^27.4.1",
    "@types/node": "^17.0.25",
    "@types/redis": "^2.8.32",
    "@types/supertest": "^2.0.12",
    "@types/ws": "^8.5.3",
    "@typescript-eslint/eslint-plugin": "^5.20.0",
    "@typescript-eslint/parser": "^5.20.0",
    "cross-env": "^7.0.3",
    "eslint": "^8.13.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "^27.5.1",
    "jest-junit": "^13.0.0",
    "jest-sonar-reporter": "^2.0.0",
    "mkdirp": "^1.0.4",
    "prettier": "^2.6.2",
    "redis-mock": "^0.56.3",
    "rimraf": "^3.0.2",
    "supertest": "^6.2.2",
    "ts-jest": "^27.1.4",
    "ts-loader": "^9.2.8",
    "ts-node": "^10.7.0",
    "tsconfig-paths": "^3.14.1",
    "typescript": "^4.6.3",
    "webpack": "^5.72.0",
    "webpack-cli": "^4.9.2"
  },
  "engines": {
    "npm": "^7",
    "node": "^14"
  }
}

Solution

  • I was doing a second try on enabling source map today and funny enough it worked. Must have missed something yesterday. Thx to Dave for bringing it up again!

    My understanding is that Jest converts TypeScript code to JavaScript before running tests [1], which leads to false alarms about code coverage because the proper lines in the original code cannot be found. Setting sourceMap=true in tsconfig.json solved the issue for me.

    However, I have a backend application and so the code is not visible to the enduser. When enabling source maps in a frontend app, beware of the security aspect as the code goes public.