Why I getting wrong code coverage in Jest (Angular)

345 Views Asked by At

I have created small project to check coverage results and have unexpected results. It's definitely a configuration error on my part, but I just can't figure out what I have to tweak in order to fix the issue. For this issue I am presenting the simplest file I can to expose the problem. The problem happens across my entire project.

Any help is greatly appreciated. Thank you!

Dependencies

{
"dependencies": {
    "@angular/animations": "^15.2.0",
    "@angular/common": "^15.2.0",
    "@angular/compiler": "^15.2.0",
    "@angular/core": "^15.2.0",
    "@angular/forms": "^15.2.0",
    "@angular/platform-browser": "^15.2.0",
    "@angular/platform-browser-dynamic": "^15.2.0",
    "@angular/router": "^15.2.0",
    "@ngrx/effects": "^15.3.0",
    "@ngrx/entity": "^15.3.0",
    "@ngrx/router-store": "^15.3.0",
    "@ngrx/store": "^15.3.0",
    "@ngrx/store-devtools": "^15.3.0",
    "bootstrap": "^5.2.3",
    "jasmine-marbles": "^0.9.1",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.12.0"
  },
  "devDependencies": {
    "@angular-builders/jest": "^15.0.0",
    "@angular-devkit/build-angular": "^15.2.2",
    "@angular-eslint/builder": "^15.2.1",
    "@angular-eslint/eslint-plugin": "^15.2.1",
    "@angular/cli": "~15.2.2",
    "@angular/compiler-cli": "^15.2.0",
    "@types/jest": "^29.4.1",
    "@typescript-eslint/eslint-plugin": "^5.54.1",
    "@typescript-eslint/parser": "^5.54.1",
    "eslint": "^8.35.0",
    "eslint-config-prettier": "^8.7.0",
    "eslint-plugin-import": "^2.27.5",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-unused-imports": "^2.0.0",
    "husky": "^4.2.5",
    "jest": "^29.3.1",
    "jest-environment-jsdom": "^29.5.0",
    "jest-jasmine2": "^29.5.0",
    "jest-junit": "^15.0.0",
    "jest-preset-angular": "^13.0.0",
    "lint-staged": "^13.2.0",
    "prettier": "^2.8.4",
    "ts-node": "10.9.1",
    "typescript": "~4.9.4"
  }
}

Jest Config

export default {
  displayName: 'coffee-project',
  testEnvironment: 'jsdom',
  extensionsToTreatAsEsm: ['.ts'],
  globals: {
    'ts-jest': {
      tsconfig: '<rootDir>/tsconfig.spec.json',
      isolatedModules: true,
    },
  },
  transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
  resetMocks: true,
  coverageProvider: 'v8',
}

Test file

import {getSelectedCoffee, selectCoffeeList} from './coffee.selectors';

const coffeeDataMock = [
  {
    "id": 9809,
    "uid": "e2bb804d-2f9c-4d4c-ac04-23d516c30e69",
    "blend_name": "Express Bean",
    "origin": "Nariño, Colombia",
    "variety": "S795",
    "notes": "balanced, smooth, graham cracker, leafy greens, medicinal",
    "intensifier": "delicate"
  },
  {
    "id": 9779,
    "uid": "545b2556-2249-4eee-bb93-16588ecfb446",
    "blend_name": "Goodbye Cake",
    "origin": "Estelí, Nicaragua",
    "variety": "Gesha",
    "notes": "wild, smooth, lemonade, tomato, cranberry",
    "intensifier": "lingering"
  },
];


export const CoffeeStateMock = {
  coffeeList: coffeeDataMock,
  selectedCoffee: coffeeDataMock[0],
  currentPage: 0,
  loader: false
};

describe('Coffee selectors', () => {
  it('should select CoffeeList', () => {
    const result = selectCoffeeList({
      'coffeeState': CoffeeStateMock
    });

    expect(result).toEqual(CoffeeStateMock.coffeeList)
  });

  it('should select CoffeeList', () => {
    const result = getSelectedCoffee({
      'coffeeState': CoffeeStateMock
    });

    expect(result).toEqual(CoffeeStateMock.selectedCoffee)
  });
})

File tested

import {createFeatureSelector, createSelector} from '@ngrx/store';
import {CoffeeState} from './coffee.state';
import {CoffeeItem} from '../definitions/interface/coffee-item.interface';

export const getCoffeeState = createFeatureSelector<CoffeeState>('coffeeState');

export const selectCoffeeList = createSelector(
  getCoffeeState,
  (state: CoffeeState): CoffeeItem[] => state.coffeeList
);

export const getSelectedCoffee = createSelector(
  getCoffeeState,
  (state: CoffeeState): CoffeeItem => state.selectedCoffee!
);

export const selectCurrentPage = createSelector(
  getCoffeeState,
  (state: CoffeeState): number => state.currentPage
);

export const getLoaderStatus = createSelector(
  getCoffeeState,
  (state: CoffeeState): boolean => state.loader
);

export const getSlicedCoffeeList = createSelector(
  selectCoffeeList,
  selectCurrentPage,
  (coffeeList, currentPage) => {
    const pageItemsLimit = 10;
    const startIndex = currentPage * pageItemsLimit;
    const endIndex = startIndex + pageItemsLimit;
    return (coffeeList || [])?.slice(startIndex, endIndex);
  }
);

Coverage results for file

100% Statements 36/36
83.33% Branches 5/6
100% Functions 0/0
100% Lines 36/36

results

Expected behaviour My code coverage should illustrate actual result

1

There are 1 best solutions below

0
Chris Barr On

So the part in your test that is highlighted in yellow is the part that is untested. In this statement: (coffeeList || []) you have no test code that triggers the 2nd half of that statement to return []. This will happen when coffeeList is null or undefined

Here's an example:

function run(input){
  const result = input || 'default';
  console.log(input, result);
}

run('foo');
run(null);
run();

To get your code coverage to 100% you need to write a test that triggers that part of the code.

Possibly something like this. This is just to give you an idea, it might not be 100% functional code here

it('should return an empty array when the coffee list is empty', () => {
  
  //Create a copy of the data
  const mockStateWithEmptyList = {...CoffeeStateMock};
  //Replace the list with nothing
  mockStateWithEmptyList.coffeeList = undefined;
  
  const result = getSelectedCoffee({
    'coffeeState': mockStateWithEmptyList 
  });

  expect(result).toEqual([])
});