This article aims to describe a really fast step-by-step way to setup Jest testing for Angular projects including debugging capabilities of Visual Studio Code.
Overview
- Why Jest?
- Prerequisites
- Removal of Karma (and Jasmine)
- Setting up Jest
- Configure Debugging in VS Code
- Outlook and References
I always thought that no one will ever read my blog posts. However, I myself will surely read my own as long as I haven't completely automated the following steps. The upcoming information is already present online but varies and is distributed over several sources*. My goal is to describe everything that is needed in one single post.
Why Jest?
The default test runner for Angular applications is Karma in combination with Jasmine as test framework. In recent times I often prefer Jest as testing framework. In my opinion it has some advantages over the default setup: It is headless out-of-the-box, less configuration is needed and it has some handy features like snapshot testing. Since Jest is headless which means that it won't spawn a browser window where you can open Chrome Dev Tools, it can get cumbersome to use console-logs when trying to debug while testing. The debug logs would spam the terminal and you will probably have to scroll a lot to find the outputs you are looking for. At this point you should think about using the great debugging features of your editor.
Some Prerequisites
This guide assumes that you are using Angular in version 8 (as it uses the new CLI builders). Moreover, I assume that we won't use Protractor as E2E testing framework. We're already living in heaven and may be using Cypress later. Therefore, we can safely remove anything related to Jasmine.
If you don't have an Angular application already, you may create one like you would normally do with the Angular CLI:
ng new <app-name>
The Cleanup
When it comes to source code or configuration, I am a huge friend of less is more. Any code snippet or configuration that is not needed or commented out can be removed. Our plan is to remove any Karma and Jasmine related dependencies and entities to always have a clean project.
Remove Protractor and Jasmine (Optional)
If you really want to use Protractor for your end-to-end tests you can skip this part. Otherwise, we will remove everything related to the E2E tests. If we have done this, we can remove everything related to Jasmine, too.
Remove the following because we won't use it anymore:
-
/e2e
folder containing all your end-to-end tests and configuration -
e2e
section fromangular.json
-
e2e
entry in theREADME.md
-
e2e
command from thescripts
in thepackage.json
Now we can safely uninstall Protractor and Jasmine dependencies:
npm uninstall protractor
npm uninstall jasmine-core jasmine-spec-reporter @types/jasmine @types/jasminewd2
Remove Karma
Since we will use Jest for our entire testing we can now remove everything that is related to Karma.
Uninstall Karma dependencies:
npm uninstall karma karma-chrome-launcher karma-coverage-istanbul-reporter
karma-jasmine karma-jasmine-html-reporter
As we don't use Karma, we can remove the following files:
karma.conf.js
src/test.ts
The Setup
We cleaned up our project from any unused resources and will now setup our toolchain to run unit tests with Jest.
Add Jest
We can start by installing Jest, it's types and new Angular builder that is used to run the Jest tests:
npm install --save-dev jest @types/jest @angular-builders/jest
Adjust Angular CLI Configuration
Replace the previous Karma builder
in the test
section of the angular.json
with the new Jest builder:
"test": {
"builder": "@angular-builders/jest:run",
"options": {
// The options and everything in here can be removed.
}
Adjust TypeScript Configurations
In your configuration for the tests (tsconfig.spec.json
) change the entry jasmine
in the types
array to jest
and remove the file src/test.ts
from the files
array. Set emitDecoratorMetadata
and esModuleInterop
to true
. The first one is needed for Angular's dependency injection to work this Jest, the latter one is suggested by a Jest warning when running your tests.
{
// ...
{
// ...
"types": [
"jest",
"node"
],
"emitDecoratorMetadata": true,
"esModuleInterop": true
},
"files": ["src/polyfills.ts"]
// ...
}
Lastly in your main base configuration tsconfig.json
also change the types
entry from jasmine
to jest
.
Add Minimal Jest Configuration (Optional)
When running Jest it complains that it cannot find a configuration file. Since I don't like such warnings we take this opportunity to add a minimal configuration that also setups up the code coverage format. I want the coverage to output a nice HTML site as an overview. You can use one of the Istanbul reporters here.
You can create the file jest.config.js
in the root of your project and add the following contents:
module.exports = {
coverageReporters: ['html', 'text', 'text-summary']
};
Adjust NPM Test Scripts (Optional)
I like to define two npm scripts to run my tests. The test
script runs all tests and produces the code coverage. The test:watch
script doesn't produce code coverage but watches for file changes (using the integrated Jest watch mode).
You can add the following to your package.json
:
"scripts": {
// ...
"test": "ng test --coverage",
"test:watch": "ng test --watch",
// ...
}
Configure Debugging in VS Code
As debugging via console-logs can be tedious, especially when your unit tests are running in headless mode, we want to use the great debugging features of Visual Studio Code. The goal is to have two launch scripts to run and debug all tests and only tests that belong to the currently opened file.
If you press F5
or switch to the Debug view in VS Code you will start one of these launch scripts. They are located in the file .vscode/launch.json
. You may add as many scripts as you want.
If not already present, create the file .vscode/launch.json
and add the following two launch configurations to this file:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest All",
"program": "${workspaceFolder}/node_modules/@angular/cli/bin/ng",
"cwd": "${workspaceFolder}",
"args": [
"test",
"--testMatch=\"**/+(*.)+(spec|test).+(ts|js)?(x)\"",
"--runInBand"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
},
{
"type": "node",
"request": "launch",
"name": "Jest Current File",
"program": "${workspaceFolder}/node_modules/@angular/cli/bin/ng",
"cwd": "${workspaceFolder}",
"args": [
"test",
"--testMatch=\"**/+(*.)+(spec|test).+(ts|js)?(x)\"",
"--testPathPattern=${fileBasenameNoExtension}",
"--runInBand",
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
}
]
}
The first script Jest All
will run all tests. The second one Jest Current File
will run all specs with a path that matches the filename (without extension) of your currently opened file. This means if you have opened either app.component.ts
or app.component.spec.ts
it will run the test app.component.spec.ts
because both app.component
and app.component.spec
match the path of your spec. Smart, isn't it? 😉
You can now set breakpoints in your specs or components that will be hit upon running these scripts. It gives you the ability to use the entire toolchain of Visual Studio Code's debugging features. You don't need to console-log all your variables, you can inspect them right inside your editor while stepping through the program execution. 🤯
(Un)known Issues
It really had some trouble with the launch configurations. Sometimes it worked. Most times it causes Jest not to find any tests. After about half a day of searching, debugging and trying out different parameters, I found out that this is caused by the default testMatch
parameter. It appears that VS Code uses the workspaceFolder
and injects it into the parameter. However, it appears that the underlying Regex is not evaluated.
The solution was to explicitly set the current working directory ("cwd": "${workspaceFolder}"
) and overwriting the default testMatch
parameter while removing the absolute path that VS Code injects itself ("--testMatch=\"**/+(*.)+(spec|test).+(ts|js)?(x)\""
).
Bright Future
As mentioned in the beginning, I will probably look up my own blog post several times. At some point in future and if I have some spare time, I want to create my own Angular Schematic to ng new
an Angular project and automate these initial steps. The schematic may then basically do the following:
-
ng new
with parameter--minimal
to prevent installing of any Karma or Jasmine related dependencies and files - Remove E2E mentions from the
README
and npm scripts for now (because they will still be added) - Add Jest dependencies
- Add
tsconfig.spec.json
and adjust thetsconfig.json
- Add
test
section toangular.json
- Add
jest.config.js
- Add
test
andtest:watch
npm scripts - Add
launch.json
I hope that you will also find some value in this article. If you have any questions or remarks, just let me know. Your feedback is very welcome!
You can find the sources for this guide on GitHub:
MrCube42 / angular-jest-vscode-debugging
Setup Jest for Angular with debugging in Visual Studio Code.
References
*There are schematics like the one from Briebug that add Jest with a single command. However, it does some things a bit different, I wanted to describe the steps to take without a schematic first and wanted to reduce the pain of setup debugging in VS Code.
- Official Angular Documentation on Testing
- Official Jest Documentation
- Angular in Depth - Angular CLI: “ng test” with Jest in 3 minutes (v2)
- VSCode Recipies - Debugging Jest with VS Code
- Nrwl Blog - Debugging Nx in VS Code
- alejandro@Medium - “No tests found, exiting with code“ debug Jest test, VSCode paraWindows.
Top comments (4)
This blog post is so good that I won't leave it outdated!
I am using angular "@angular/core": "14.2.10" and the vscode debug launch.js script stopped working giving the message:
Error: Unknown arguments: testMatch, testPathPattern, runInBand
The problem is that
Support for camel case arguments has been deprecated and will be removed in a future major version.
Use '--run-in-band' instead of '--runInBand'.
Solution
Replace camelCase with kebab-case. Here is the
Debug Jest Current File
script updated:Thank you @mrcube42 for this awesome post!
Will cypress be better for e2e.
As often, it depends: For example, currently Internet Explorer is not supported. Then you have to go with something Selenium-based like Protractor. The developer experience of Cypress is just amazing; it feels quite natural, powerful and I find it less fragile. Take a look at the official website (cypress.io/) and just give it a try. The initial setup and your first tests should take just a few minutes to hours. You could even use Briegbug's schematic for your Angular project (npmjs.com/package/@briebug/cypress...).
Thank you for the insight David! I will definitely give cypress a go for my next project and also the schematic :)