4
votes

I'm working on a project that we recently started in Angular 9, generated using Angular CLI. I want to run my tests using Jest instead of Karma, because in my opinion, Karma is too much work to set up in your build pipelines (I've had many wasted hours trying to get Karma to run a couple of years ago).

I'm getting an error though when I try to run Jest:

ReferenceError: Zone is not defined

      at node_modules/zone.js/dist/zone.js:670:5
      at performance (node_modules/zone.js/dist/zone.js:8:9)
      at Object.<anonymous> (node_modules/zone.js/dist/zone.js:9:2)

The steps I took to install Jest are:

  1. Install Jest itself

npm install -D jest jest-preset-angular @types/jest ts-jest

  1. Remove all karma/jasmine libraries from package.json

  2. Updated tsconfig.spec.json:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/spec",
    "types": [
      "jest",
      "node"
    ],
    "esModuleInterop": true,
    "emitDecoratorMetadata": true
  },
  "files": [
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.spec.ts",
    "src/**/*.d.ts"
  ]
}
  1. Add jest.config.js:
const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig');

module.exports = {
  preset: 'jest-preset-angular',
  roots: ['<rootDir>/src/'],
  testMatch: ['**/+(*.)+(spec).+(ts)'],
  setupFilesAfterEnv: ['<rootDir>/src/setupJest.ts'],
  collectCoverage: true,
  coverageReporters: ['html'],
  coverageDirectory: 'coverage/my-app',
  moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths || {}, {
    prefix: '<rootDir>/'
  })
};
  1. Remove karma.conf.js

  2. Remove test from architect in angular.json

  3. Remove test.ts from ./src

  4. Change "test": "ng test" to "test": "jest"

  5. Added setupJest.ts in ./src:

import 'jest-preset-angular';

Object.defineProperty(window, 'CSS', {value: null});
Object.defineProperty(window, 'getComputedStyle', {
  value: () => {
    return {
      display: 'none',
      appearance: ['-webkit-appearance']
    };
  }
});

Object.defineProperty(document, 'doctype', {
  value: '<!DOCTYPE html>'
});
Object.defineProperty(document.body.style, 'transform', {
  value: () => {
    return {
      enumerable: true,
      configurable: true
    };
  }
});

An example of a test that fails to run is this one:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { MenuComponent } from './menu.component';

describe('MenuComponent', () => {
  let component: MenuComponent;
  let fixture: ComponentFixture<MenuComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ MenuComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(MenuComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

I can't figure out what I am doing wrong. I tried several tutorial, also one using @angular-builders/jest, but none have worked so far. I have a feeling that maybe the transition to using the Ivy renderer is causing problems, but I am not sure about that. I can't seem to find anyone on Google with the same problem so this brings me to StackOverflow. Does anybody here know how to fix the error?

[edit]

I created a minimal preproduction of my problem at https://github.com/kayvanbree/jest-problem

[edit]

The answers to this question did not help by the way: How to fix "zone is not defined" in this project

2

2 Answers

1
votes

Turns out this was a matter of versions.

I had installed:

"@types/jest": "^25.2.2",
"jest": "^26.0.1",

It seems like these versions don't want to work together. It also gives a warning that jest 26 has not been tested with ts-jest.

Downgrading Jest to ^25.0.0 fixed the problem.

0
votes

Please try to add the "provide: NgZone" in your configureTestingModule:

    beforeEach(async(() => {
       TestBed.configureTestingModule({
         declarations: [ MenuComponent ],
         { provide: NgZone, useValue: { run(fn): any { return fn(); } }},
       })
       .compileComponents();
    }));

If this isn't working well:

Just a quick test: can you add this 'noop' config to your app bootstrapping?

platformBrowserDynamic()
    .bootstrapModule(AppModule, { ngZone: 'noop' })
    .catch(console.error});

AND please comment this line out in polyfill.ts

// import 'zone.js/dist/zone';  // Included with Angular CLI.