Migrate unittest from Jasmine to Jest

How to migrate a component unit tests from Jasmine to Jest

Migration steps

Step 1. Delete references to karma and jasmine

  • Delete the files: src/karma.conf.js and src/test.ts

  • In package.json remove the karma and jasmine packages from devDependencies.

    For example:

    "@types/jasmine": "~3.5.0",
    "@types/jasminewd2": "~2.0.3",
    "jasmine-core": "~3.6.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~5.0.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~3.0.2",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    
  • Uninstall these dependencies:

    npm uninstall @types/jasmine @types/jasminewd2 jasmine-core jasmine-spec-reporter karma karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-jasmine-html-reporter
    

Step 2. Install jest packages

Execute the following command:

npm i -D jest @types/jest @angular-builders/jest

Step 3. Configure jest

  • Add a file named jest.config.js (in the same folder as package.json)
    More information: https://jestjs.io/es-ES/docs/configuration#opciones

    module.exports = {
        preset: 'jest-preset-angular',
        setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
        collectCoverage: false,
        coverageDirectory: 'coverage/testing-in-jest',
        testMatch: [
          '<rootDir>/src/**/*.spec.ts',
        ],
        coverageReporters: ['lcovonly', 'text-summary', 'cobertura'],
        coverageThreshold: {
            global: {
                statements: 10,
                lines: 10,
                branches: 10,
                functions: 10
            }
        },
        reporters: ['default'],
    };
    
  • Add file test-config.helpers.ts

    import { TestBed } from '@angular/core/testing';
    
    type CompilerOptions = Partial<{
      providers: any[];
      useJit: boolean;
      preserveWhitespaces: boolean;
    }>;
    export type ConfigureFn = (testBed: typeof TestBed) => void;
    
    export const configureTests = (configure: ConfigureFn, compilerOptions: CompilerOptions = {}) => {
      const compilerConfig: CompilerOptions = {
        preserveWhitespaces: false,
        ...compilerOptions,
      };
    
      const configuredTestBed = TestBed.configureCompiler(compilerConfig);
    
      configure(configuredTestBed);
    
      return configuredTestBed.compileComponents().then(() => configuredTestBed);
    };
    
  • Add file setup-jest.ts
    More information: https://jestjs.io/es-ES/docs/configuration#setupfiles-array

    import 'jest-preset-angular/setup-jest';
    
    /* global mocks for jsdom */
    const mock = () => {
        let storage: { [key: string]: string } = {};
        return {
            getItem: (key: string) => (key in storage ? storage[key] : null),
            setItem: (key: string, value: string) => (storage[key] = value || ''),
            removeItem: (key: string) => delete storage[key],
            clear: () => (storage = {}),
        };
    };
    
    Object.defineProperty(window, 'localStorage', { value: mock() });
    Object.defineProperty(window, 'sessionStorage', { value: mock() });
    Object.defineProperty(window, 'getComputedStyle', {
        value: () => ['-webkit-appearance'],
    });
    
    Object.defineProperty(document.body.style, 'transform', {
        value: () => {
            return {
                enumerable: true,
                configurable: true,
            };
        },
    });
    

Step 4. Change src/tsconfig.spec.json

  • Replace jasmine with jest in the types array

  • Add module: commonjs to the compilerOptions

  • Remove test.ts from the files array

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "outDir": "./out-tsc/spec",
        "types": [
            "jest",
        ],
        "module": "commonjs",
        "emitDecoratorMetadata": true,
        "allowJs": true
    },
    "files": [
        "src/polyfills.ts"
    ],
    "include": [
        "src/**/*.spec.ts",
        "src/**/*.d.ts"
    ]
}

Step 5. Edit your angular.json file

Step 6. Migrate test with jest-codemods

  • Execute the following command:

    npx jest-codemods -f ./src/app/*.spec.ts

    ? Which parser do you want to use? TypeScript
    ? Which test library would you like to migrate from? Jasmine: globals
    ? Are you using the global object for assertions (i.e. without requiring them) No, I use import/require statements for my current assertion libr
    ary
    ? Will you be using Jest on Node.js as your test runner? Yes, use the globals provided by Jest (recommended)
    Executing command: jscodeshift -t /Users/moasl/.npm/_npx/13840/lib/node_modules/jest-codemods/dist/transformers/jasmine-globals.js ./src/app/app.component.spec.ts --ignore-pattern node_modules --parser ts --extensions=ts
    
    Executing command: jscodeshift -t .npm/_npx/14583/lib/node_modules/jest-codemods/dist/transformers/jasmine-globals.js ./src/app/app.component.spec.ts --ignore-pattern node_modules --parser ts --extensions=ts
    Processing 1 files...
    Spawning 1 workers...
    Sending 1 files to free worker...
    All done.
    Results:
    0 errors
    0 unmodified
    0 skipped
    1 ok
    Time elapsed: 1.286seconds
    
  • After all of these changes, it is recommended to delete your node_modules folder and run npm install again.

  • At this stages, you may have to change some test manually, for example, if jest does not have toBeTrue or toBeFalse.

Interesting library: jest-dom

Common errors

Converting circular structure to JSON

  • If you lose some import or injection in your component test, you will get this error type:

    (node:71567) UnhandledPromiseRejectionWarning: TypeError: Converting circular structure to JSON
        at JSON.stringify (<anonymous>)
        at process.target._send (internal/child_process.js:735:23)
        at process.target.send (internal/child_process.js:634:19)
        at reportSuccess (/Users/moasl/PROYECTOS/AURA/aura-channels-factory/packages/aura-channels-libraries/node_modules/jest-runner/node_modules/jest-worker/build/workers/processChild.js:67:11)
    (node:71567) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
    (node:71567) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
    
  • If you run the test with --detectOpenHandles option, you will get more information to solve the error:

    $ ng test --detectOpenHandles aura-channels-views
    .....
    FAIL  projects/aura-channels-views/src/lib/components/alfred/alfred.component.spec.ts
      ● AlfredComponentComponent › should render title
    
        Found the synthetic property @alfredAnimationState. Please include either "BrowserAnimationsModule" or "NoopAnimationsModule" in your application.
    
          at checkNoSyntheticProp (../packages/platform-browser/src/dom/dom_renderer.ts:269:11)
    ....
    

More information: Jest documentation –detectOpenHandles.