Jest and Karma in one Angular project

Oleg Sytnik
3 min readSep 8, 2022

Why do we need two testing tools, like Jest and Karma stand together? There’s one good reason. We do know that Karma launches a browser every time we run tests. That means it takes some time to raise our testing environment. And it’s the main difference between these tools. Jest doesn’t need to operate with DOM to perform tests.

So, do my tests run faster with Jest? Yes, they do. But why then should you keep Karma on a project, if it hasn’t a performance advantage? First of all, with Jest you can run into error messages like this when some test cases require window object:

console.error
Error: Uncaught [TypeError: window.matchMedia is not a function]

There is a workaround for it, but it looks ugly 😕

Also, it is hard to debug manually with no browser. Seems like Karma is a better choice for UI tests and Jest is better for unit tests.

You don’t need a UI test for service but for a component probably do. That means you can make separate files for it now:

my-component.component.spec.ts // Unit tests
my-component.component.ui.spec.ts // UI tests

So how to set up both testing tools for an Angular project?

Karma

Here we have karma configuration (src/karma.conf.js). You can find the list of available properties and a description of what they do here.

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
reporters: ['progress', 'kjhtml', 'coverage'],
coverageReporter: {
includeAllSources: true,
dir: '../karma-coverage',
reporters: [
{
type: 'lcov',
subdir: '.',
},
],
},
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadless'],
singleRun: true,
customLaunchers: {
ChromeHeadless: {
base: 'Chrome',
flags: [
'--no-sandbox',
'--disable-gpu',
'--headless',
//needed so that chrome will not exit immediately in docker
'--remote-debugging-port=9222',
],
},
},
});
};

Another special karma file for loading spec files is src/test.ts.

// This file is required by karma.conf.js and loads recursively all the .spec and framework files

import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';

declare const require: any;

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);
// Then we find all the tests.
const context = require.context('./', true, /\.ui\.spec\.ts$/);

// And load the modules.
context.keys().map(context);

In the same directory should put src/tsconfig.ui.spec.json file. The general idea is to specify the type as jasmine and add glob in include prop as **/*.ui.spec.ts. All files matched this ending will run as karma tests.

{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine"
],
"skipLibCheck": true
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.ui.spec.ts",
"**/*.d.ts"
]
}

Jest

The configuration file (/jest.config.ts) looks like:

module.exports = {
preset: 'jest-preset-angular',
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
globalSetup: 'jest-preset-angular/global-setup',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/src/tsconfig.spec.json',
},
},
testRunner: 'jest-jasmine2',
roots: ['<rootDir>'],
testMatch: ['**/*.done.spec.ts'],
moduleDirectories: ['node_modules', '<rootDir>'],
collectCoverage: true,
coverageDirectory: './jest-coverage',
moduleNameMapper: {
'^lodash-es$': 'lodash',
},
};

For more details use the link.

Jest won’t work correctly with Angular without this preset (/setup-jest.ts) we specified in prop setupFilesAfterEnv.

import 'jest-preset-angular/setup-jest';

Obviously, we should add tsconfig for jest as well. In the end, we will have:
src/tsconfig.ui.spec.ts for karma
src/tsconfig.spec.ts for jest

{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jest",
"node"
]
},
"files": [
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

Here we have types specified as jest and node. All matched spec files end with **/*.spec.ts will run by jest.

So now we have karma and jest works together in one project.

--

--