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.

Jest @ Angular

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.

One comment

  1. 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)

Comments

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.