Converting an Angular Project to the Jest Framework
Faster and more convenient Angular unit testing awaits

I am a full stack developer living in Chicago, IL. I currently work for TransUnion, where I design and develop enterprise web applications primarily using Java and TypeScript/Angular. I recently learned Go in my spare time, and am excited to learn and explore more of that language. In my free time, I like to run, cook, bike around Chicago, and sail on Lake Michigan.
Ugh, unit tests are your first post?
Yes! I am a huge fan of unit tests, and I'm here today to hopefully make your life easier writing them in Angular.
As developers, we are responsible for not only writing good, clean code but also comprehensive unit tests to accompany that code. Often times, unit tests can feel a bit like a chore, and they are not necessarily a magic bullet at preventing production defects. However, as I like to say: "they help me sleep at night." By this mean, I can be relatively confident that if I write comprehensive unit tests, which cover all lines and branches, my code is functioning the way I believe it should be.
Simply put, unit tests are important. Even if you're just playing around with hobby projects, developing skill for writing effective unit tests is arguably just as important as writing the code to begin with, and you can rest assured any professional development job will, at the very least, require you to achieve a certain line coverage percentage (we also enforce branch percentage on my team).
Ok, so you mentioned Angular...
Yes, I did. When I first learned Angular, I was disappointed at how cumbersome it was to write unit tests. Not only do we have to juggle multiple different querySelector() strategies and dive in through debugElements constantly just to put a specific element under test, we also had to spin up a full-on webserver just to run the tests. That's to say nothing of setting up your TestBed to begin with, which as a beginner, was quite the challenge for me to wrap my head around.
Well, a combination of Spectator and NgMocks largely solves the issues related to the cumbersome API and TestBed dependencies. That topic alone is worth another article, but today we are here to talk about the Jest Framework, which addresses my webserver complaint.
What is Jest?
Jest is a delightful JavaScript Testing Framework with a focus on simplicity. -- jestjs.io
Beyond that, it's an alternative to Jasmine/Karma that lets you run tests one at a time using a Visual Studio Code extension. The end result is a unit testing experience that's much more similar to Java IDEs: click run at whatever level you desire (test, file, project), and see your results.
Benefits
Does not require a webserver to run unit tests. Makes running individual one-off tests much faster.
Do not need to juggle cumbersome
fit()andfdescribe()code modifications to run individual tests or suites. Simply click Run.No need for a Headless browser to run in a CI/CD environment.
Claims to be 2-3x faster than Karma, and while this is usually true locally, in Jenkins I've observed it to be about the same speed.
Has a "watch" mode, which will automatically run unit tests for files that have changed since the last commit.
Downsides
Jest uses JSDOM, which is not a "real" browser. So if you have tests that are specifically testing browser compatibility, Jasmine + Karma remains a better choice as they simulate actual browser behavior.
If you use
window.localStorage, you will need setup a global mock due to the above limitation. This fantastic article has an example.I have experienced challenges getting my Jest tests to pass in Jenkins initially, although this was resolved after some troubleshooting. Still, the fact they passed locally and failed in the CI/CD environment is worrisome. (More on that later.)
Will require test code changes to get old tests running and passing. If you are already using Spectator and ngMocks to their fullest extent, these will be fairly minimal.
Ok, great - you are a unit test nerd and like Jest. Why write a guide about it?
When I set about migrating a fairly large (1k+ unit tests, 99% coverage) project to Jest, I found myself requiring many different guides and posts to achieve the same level of functionality I had grown used to with Jasmine in terms of matchers and the ability to conditionally mock. Some of the information out there is also old, and may not be applicable anymore, or configured differently now.
This guide represents my experience, which took place just a few months ago. So everything below is relatively current, and I just tested it again as I wrote this guide.
How to Setup Jest
Remove Jasmine and Karma
First, you begin by removing Jasmine and Karma.
$ npm uninstall jasmine-core @types/jasmine karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter
Note: these are the Jasmine and Karma-related packages installed on a fresh Angular 13 project. If you have any others in your project, be sure to remove those as well.
Remove configuration files
You should also remove the following files:
- src/test.js
- karma.conf.js
Install Jest and supporting libraries
Install the following libraries:
$ npm install jest jest-preset-angular @types/jest @testing-library/jest-dom jest-when @types/jest-when --save-dev
Not all of these are required, but installing them all will get you to effectively the same functionality that Karma/Jasmine provided. If you did not use .withArgs() in Jasmine, you do not need jest-when, for example.
I would also recommend installing Jest CLI globally, this has helped reduce some issues getting tests to run again after re-installing a project:
$ npm install -g jest-cli
Update configurations
- If there is a
jestfield present in your package.json file, remove it. You will be replacing this with a setup-jest.ts file. - Update tsconfig.spec.json by replacing
jasminewithjestand adding@testing-library/jest-domto thetypesarray (undercompilerOptions):
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jest",
"@testing-library/jest-dom"
]
},
- Also, remove
src/test.tsfromfiles:
"files": [
"src/polyfills.ts"
],
- Add the following to tsconfig.json under
compilerOptions:
"esModuleInterop": true,
- Create a new file called jest.config.js in your project's root directory, and populate it with the following:
// this will force Jest to use the AOT compiler in Jenkins - uncomment if you have issues with test failures in Jenkins - see below for more info
// require('jest-preset-angular/ngcc-jest-processor');
const tsJestPreset = require('jest-preset-angular/jest-preset').globals['ts-jest'];
module.exports = {
preset: "jest-preset-angular",
roots: ['src'],
setupFilesAfterEnv: ["<rootDir>/src/setup-jest.ts"],
verbose: true,
testURL: "http://localhost",
globals: {
'ts-jest': {
...tsJestPreset,
tsconfig: "<rootDir>/tsconfig.spec.json"
}
}
}
The
require('jest-preset-angular/ngcc-jest-processor')line above was necessary to get a work project's tests running successfully in Jenkins. For some reason, Jenkins appeared to be using the JIT compiler while running Jest unit tests. More information available in this GitHub Issue.
- Create a file called jsconfig.json (note this starts with a j, is different than tsconfig.json) in your project's root directory and populate it with the following:
{
"typeAcquisition": {
"include": [
"jest"
]
}
}
I had to do this to get Visual Studio Code's IntelliSense to stop trying to use Jasmine for everything when I converted an old project. I do not think this will be necessary if you remove all traces of Jasmine/Karma first (and it was not necessary for a new project), but I figured I would leave it here just in case any poor soul faces this issue.
- Create a file called setup-jest.ts in your project's
srcdirectory and populate it with the following:
import 'jest-preset-angular/setup-jest';
import '@testing-library/jest-dom/extend-expect';
import 'jest-when'
Remove the entire
testblock from angular.json. This is strictly optional, but if you runng testit will fail.Finally, if you are using Spectator (and you should be!), update the
schematicsproperty in angular.json to include the following:
"@ngneat/spectator:specator-component": {
"test": "jest"
}
package.json Changes
The changes to package.json are very straightfoward, just update the test script:
"test": "jest --coverage", // I like to add --coverage to display a coverage report
"test:watch": "jest --watch", // optional - automatically runs tests as files change
"test:ci": "jest --runInBand" // optional - for CI/CD environments
You can then use npm run test then to run all tests, which should be quite fast - remember, no webserver!
jest --watch (npm run test:watch above) is pretty cool: as you make changes to your source files, Jest will automatically run tests for that file, as well as any related files.
Visual Studio Code Extensions
To get the most out of Jest, you will want to install the following extensions on Visual Studio Code:
I also like to set jest.autoRun to off in my settings.json, otherwise all tests will run at start-up which for large projects can be cumbersome.
IMPORTANT: Make sure you are opening your project in VS Code from the project's root directory (the same folder
srcis found in). Otherwise, none of the Jest UI extensions will work. When I created a new Angular project via a Docker Container, VS was opening/workspace/angularinstead of/workspace/angular/angular-demo(my project's root), and as a result, none of the test extensions above worked.
Once you have the above extensions installed, you can easily run your unit tests from either the newly-added Testing screen, or by clicking the little green play icons within your spec files!
...but they may fail, read on for the bad news.
Spec File Updates
Last but not least, if you have existing unit tests, unfortunately some of your Jasmine implementations will need to be converted.
Using Spectator is immensely helpful with this effort, as you can simply change your
@ngneat/spectatorimports to@ngneat/spectator/jestand all Spectator-related functionality will automatically be converted!
Here are some of the updates I have had to make:
| Jasmine | Jest |
spyOn(class, 'method') | jest.spyOn(class, 'method') |
spy = jasmine.createSpyObj('name', ['key']) | spy = { key: jest.fn() } |
.and.returnValue() | .mockReturnValue() |
spy.key.withArgs([args]).and.returnValue() | when (spy.key).calledWith([...args]).mockReturnValue() |
spyOn(...).and.callFake(() => {...}) | jest.spyOn(...).mockImplementation(() => {}) |
jasmine.any, jasmine.objectContaining | expect.any, expect.objectContaining |
element.innerText | element.textContent |
Note: thanks to this blog post for helping me get started with most of the above material.
Final Thoughts
So...is it worth it? Personally, I think so. It took a while to convert over 1k existing tests to be Jest-friendly, but once I got rolling it was largely the same few different scenarios that had to be converted over and over again. The the ability to run tests one-off quickly without having to use fit() or fdescribe() and spin up a webserver has really made the unit testing process in Angular a lot smoother and enjoyable.
You can check out my Angular Demo Project here on GitHub to see all of the above in action. Have fun!
And feel free to leave comments with any questions or comments. Thanks for reading!