Do you want to improve Angular unit test performance? Then you might be interested in Jest, i.e. Facebook’s unit test framework. In this article, we will have a word on test performance, before we show how to replace Karma by Jest right after creating a new project with Angular CLI.
Why Jest?
According to jestjs.io, Jest is a JavaScript Testing Framework built by Facebook with a focus on simplicity. Studies suggest that Jest is substantially faster than Karma. The author of this blog post reports a performance gain factor of 2.5. The reason for the speed gain seems to be caused by the fact that Jest does not need to run the tests in a browser.
Let us move on and replace Karma by Jest now.
Step 0: Install and configure NPM
NPM comes with NodeJS. Get the latest LTS NodeJS Version from https://nodejs.org/en/. After installing, we check the NPM version. In our case, we have installed NodeJS v 10.16.0, which comes with NPM version 6.9.0.
npm -v # output: 6.12.1
In case you are located behind a proxy and/or you are using your private registry, you need to specify this in your personal .npmrc file:
npm config edit -------- ... https-proxy=http://example.proxy:8080/ proxy=http://example.proxy:8080/ strict-ssl=false registry=http://example-registry:9091/repository/npm_folder ... --------
Step 1: Create a new Project
A new project is installed by an ng new
command:
ng new my-project && cd my-project
Step 2 (optional): Code Tracking with GIT
On Github, we create a new project and push the changes. As a minimum, we recommend performing the first three commands. If you do so, you can better track what actually has changed afterward.
# git init # is not needed, since it is done automatically by the ng new command git add . git commit -am'Initial Commit nach ng new' git remote add origin ssh://git@.../my-project.git git push --set-upstream origin master
Step 3: Verify Unit Tests with Karma
Before replacing Karma by Jest, we ensure that the unit tests work correctly with Karma:
ng test --watch=false
If we omit the --watch=false
option, Karma will start a Browser for continuous testing. It will watch for any changes in any of the source files and it will trigger the re-evaluation of the unit tests. Here, we do not need this option, since we just want to verify that the unit tests are working, before we move on to replacing Karma by Jest.
Step 4 (if needed): Configure Proxy on Project Folder
Even though we have configured the personal npmrc
settings, we also need to do it in the project itself (due to a bug, though?):
cat <<EOF > .npmrc # proxy + registry: https-proxy=http://example.proxy:8080/ proxy=http://example.proxy:8080/ registry=http://example-registry:9091/repository/npm_folder # optional: fetch-retries=2 fetch-retry-factor=10 fetch-retry-mintimeout=10000 fetch-retry-maxtimeout=60000 # method not allowed => disable audit = false # loglevel loglevel = info EOF
Step 5: Replace Karma by Jest
The briebug/jest-schematic
command will replace Karma by Jest:
ng add @briebug/jest-schematic
Optional: A git status and a git diff can help to understand, which changes have been introduced:
$ git status On branch master Changes not staged for commit: (use "git add/rm ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) deleted: karma.conf.js modified: package-lock.json modified: package.json deleted: src/test.ts modified: tsconfig.spec.json Untracked files: (use "git add ..." to include in what will be committed) src/setup-jest.ts src/test-config.helper.ts no changes added to commit (use "git add" and/or "git commit -a")
And here the git diff of the package.json file:
# GIT DIFF diff --git a/package.json b/package.json index 9b5eea4..1e9e683 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,10 @@ "ng": "ng", "start": "ng serve", "build": "ng build", - "test": "ng test", + "test": "jest", "lint": "ng lint", - "e2e": "ng e2e" + "e2e": "ng e2e", + "test:watch": "jest --watch" }, "private": true, "dependencies": { @@ -19,6 +20,7 @@ "@angular/platform-browser": "~8.2.14", "@angular/platform-browser-dynamic": "~8.2.14", "@angular/router": "~8.2.14", + "@briebug/jest-schematic": "^2.1.1", "rxjs": "~6.4.0", "tslib": "^1.10.0", "zone.js": "~0.9.1" @@ -28,20 +30,48 @@ "@angular/cli": "~8.3.19", "@angular/compiler-cli": "~8.2.14", "@angular/language-service": "~8.2.14", + "@types/jest": "24.0.23", "@types/node": "~8.9.4", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "codelyzer": "^5.0.0", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", - "karma": "~4.1.0", - "karma-chrome-launcher": "~2.2.0", - "karma-coverage-istanbul-reporter": "~2.0.1", - "karma-jasmine": "~2.0.1", - "karma-jasmine-html-reporter": "^1.4.0", + "jest": "24.9.0", + "jest-preset-angular": "8.0.0", "protractor": "~5.4.0", "ts-node": "~7.0.0", "tslint": "~5.15.0", "typescript": "~3.5.3" + }, + "jest": { + "preset": "jest-preset-angular", + "roots": [ + "src" + ], + "transform": { + "^.+\\.(ts|js|html)$": "ts-jest" + }, + "setupFilesAfterEnv": [ + "<rootDir>/src/setup-jest.ts" + ], + "moduleNameMapper": { + "@app/(.*)": "<rootDir>/src/app/$1", + "@assets/(.*)": "<rootDir>/src/assets/$1", + "@core/(.*)": "<rootDir>/src/app/core/$1", + "@env": "<rootDir>/src/environments/environment", + "@src/(.*)": "<rootDir>/src/src/$1", + "@state/(.*)": "<rootDir>/src/app/state/$1" + }, + "globals": { + "ts-jest": { + "tsConfig": "<rootDir>/tsconfig.spec.json", + "stringifyContentPathRegex": "\\.html$", + "astTransformers": [ + "jest-preset-angular/build/InlineFilesTransformer", + "jest-preset-angular/build/StripStylesTransformer" + ] + } + } } }
We can see that the npm test script has been moved from Karma to Jest. Moreover, jest-schematic has created a new Jest configuration section. In the optional refactoring section below, we will move the Jest configuration to a separate file.
Step 6: Test Unit Tests with Jest
Now the unit tests should still be successful:
npm test
This should produce an output similar to follows:
# OUTPUT: npm info it worked if it ends with ok npm info using npm@6.9.0 npm info using node@v10.16.0 npm info lifecycle briebug-project@0.0.0~pretest: briebug-project@0.0.0 npm info lifecycle briebug-project@0.0.0~test: briebug-project@0.0.0 > briebug-project@0.0.0 test C:\VeitsO\git\sete\briebug-project > jest <<<<<<<<<<<<<<<<<<<<< jest instead of karma! <<<<<<<<<<<<<<<< PASS src/app/app.component.spec.ts AppComponent ? should create the app (226ms) ? should have as title 'briebug-project' (142ms) ? should render title (176ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 4.206s, estimated 5s Ran all test suites. npm info lifecycle briebug-project@0.0.0~posttest: briebug-project@0.0.0 npm timing npm Completed in 5692ms npm info ok
That’s it; more or less. The following steps are optional.
Schritt 7 (optional): GIT push
To save the changes, we can push the changes to GIT:
git add . git commit -m'briebug: replacing karma by jest' git push
Step 8 (optional): Refactoring & Optimizations
In this chapter, we will introduce some refactoring steps:
- Move the jest configuration from the package.json to a separate Jest config file
- Fix the versions of the node modules briebug has introduced (they are still on „latest“)
Step 8.1: Move Jest Configuration to a separate File
Step 8.1.1: Create Jest Configuration File
To create the new jest.config.js file, just cut&paste following code to a Bash shell:
cat <<'EOF' > jest.config.js module.exports = { preset: 'jest-preset-angular', testEnvironment: 'jsdom', setupFilesAfterEnv: [ '<rootDir>/src/setupJest.ts' ], moduleNameMapper: { '^lodash-es$': 'lodash' }, testMatch: [ '<rootDir>/src/**/*.spec.ts', ], collectCoverage: false, collectCoverageFrom: [ '**/src/**/*.ts', '!**/node_modules/**', '!**/src/**/*.module.ts', '!test/**', '!**/polyfills.ts', '!**/environments/**', '!**/src/setupJest.ts' ] }; EOF
Step 8.1.2: Clean package.json
The Jest configuration is now duplicate: we can find it in the jest.config.js file as well as in the package.json file. Let us get rid of the latter: we remove the „jest“ section. After that, the git diff should look like follows:
git diff package.json --- a/package.json +++ b/package.json @@ -30,48 +30,19 @@ <...> - }, - "jest": { - "preset": "jest-preset-angular", - "roots": [ - "src" - ], - "transform": { - "^.+\\.(ts|js|html)$": "ts-jest" - }, - "setupFilesAfterEnv": [ - "/src/setup-jest.ts" - ], - "moduleNameMapper": { - "@app/(.*)": "/src/app/$1", - "@assets/(.*)": "/src/assets/$1", - "@core/(.*)": "/src/app/core/$1", - "@env": "/src/environments/environment", - "@src/(.*)": "/src/src/$1", - "@state/(.*)": "/src/app/state/$1" - }, - "globals": { - "ts-jest": { - "tsConfig": "/tsconfig.spec.json", - "stringifyContentPathRegex": "\\.html$", - "astTransformers": [ - "jest-preset-angular/build/InlineFilesTransformer", - "jest-preset-angular/build/StripStylesTransformer" - ] - } - } } }
Step 8.2: Fix Versions
In the briebug
step above, several packages were added to the package.json
with the „latest“ version. For improving the reproducibility, we fix those versions like follows:
npm i -D jest @types/jest jest-preset-angular ts-jest
With that, the following changes have been introduced to the package.json (here, we have reordered some lines, so the deleted and the added lines can be compared more easily):
git diff package.json diff --git a/package.json b/package.json index 9a188e0..9721dc8 100644 --- a/package.json +++ b/package.json @@ -30,16 +30,17 @@ "@angular/cli": "~8.3.18", "@angular/compiler-cli": "~8.2.13", "@angular/language-service": "~8.2.13", - "@types/jest": "latest", + "@types/jest": "24.0.23", - "@types/node": "~8.9.4", + "@types/node": "~8.9.4", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "codelyzer": "^5.0.0", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", - "jest": "latest", + "jest": "24.9.0", - "jest-preset-angular": "latest", + "jest-preset-angular": "8.0.0", "protractor": "~5.4.0", + "ts-jest": "24.1.0", "ts-node": "~7.0.0", "tslint": "~5.15.0", "typescript": "~3.5.3"
Step 8.3: Verify that the Unit Tests still work
After the changes above, the unit tests should still work:
npx jest # or: npm test
The Output should look similar to follows:
PASS src/app/app.component.spec.ts AppComponent ? should create the app (218ms) ? should have as title 'briebug-project' (143ms) ? should render title (190ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 7.503s Ran all test suites.
Summary
We have shown how to replace the Karma unit test framework by the somewhat more modern Jest unit test framework built by Facebook. We have seen, that a single ng add @briebug/jest-schematic
does the main work. However, we have shown how to refactor the code and move the jest configuration from the package.json into its own file.
Hi I tried migrating from Karma to Jest by following your page. I am getting this error. Could you please help me.
TypeError: tslib_1.__classPrivateFieldSet is not a function
at new NgJestTransformer (/Users/aplklat492496/Documents/OHL/oha/oha-web-frontend/node_modules/jest-preset-angular/build/ng-jest-transformer.js:18:17)
at Object.createTransformer (/Users/aplklat492496/Documents/OHL/oha/oha-web-frontend/node_modules/jest-preset-angular/build/index.js:5:30)
at /Users/aplklat492496/Documents/OHL/oha/oha-web-frontend/node_modules/@jest/transform/build/ScriptTransformer.js:392:39
at async Promise.all (index 0)
at async ScriptTransformer.loadTransformers (/Users/aplklat492496/Documents/OHL/oha/oha-web-frontend/node_modules/@jest/transform/build/ScriptTransformer.js:376:5)
at async createScriptTransformer (/Users/aplklat492496/Documents/OHL/oha/oha-web-frontend/node_modules/@jest/transform/build/ScriptTransformer.js:1114:3)
at async /Users/aplklat492496/Documents/OHL/oha/oha-web-frontend/node_modules/@jest/core/build/TestScheduler.js:264:31
at async Promise.all (index 0)
at async TestScheduler.scheduleTests (/Users/aplklat492496/Documents/OHL/oha/oha-web-frontend/node_modules/@jest/core/build/TestScheduler.js:259:5)
at async runJest (/Users/aplklat492496/Documents/OHL/oha/oha-web-frontend/node_modules/@jest/core/build/runJest.js:407:19)