Jest is a Node.js-based test runner that executes tests without a browser — using jsdom to simulate the DOM. For Angular, jest-preset-angular handles TypeScript compilation, Angular-specific transforms, and TestBed compatibility. The result is significantly faster tests: a typical BlogApp test suite that takes 60 seconds in Karma completes in 15-20 seconds with Jest, with instant file-level re-runs in watch mode.
Migrating to Jest
// ── Installation ──────────────────────────────────────────────────────────
// npm install --save-dev jest jest-preset-angular @types/jest
// npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine
// karma-jasmine-html-reporter @types/jasmine
// ── jest.config.ts ────────────────────────────────────────────────────────
import type { Config } from 'jest';
const config: Config = {
preset: 'jest-preset-angular',
setupFilesAfterFramework: ['<rootDir>/setup-jest.ts'],
testEnvironment: 'jsdom',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: /\.html$/,
},
],
},
moduleNameMapper: {
// Map Angular path aliases
'^@core/(.*)$': '<rootDir>/src/app/core/$1',
'^@shared/(.*)$': '<rootDir>/src/app/shared/$1',
'^@features/(.*)$': '<rootDir>/src/app/features/$1',
},
collectCoverageFrom: [
'src/app/**/*.ts',
'!src/app/**/*.module.ts',
'!src/environments/**',
],
coverageThresholds: {
global: {
statements: 80,
branches: 70,
functions: 85,
lines: 80,
},
},
testMatch: ['**/*.spec.ts'],
};
export default config;
// ── setup-jest.ts ─────────────────────────────────────────────────────────
import 'jest-preset-angular/setup-jest';
// Add any global test setup here (e.g., custom matchers)
// ── tsconfig.spec.json — switch from jasmine to jest types ────────────────
// {
// "extends": "./tsconfig.json",
// "compilerOptions": {
// "types": ["jest"], // was ["jasmine"]
// ...
// }
// }
// ── package.json — update test scripts ────────────────────────────────────
// "scripts": {
// "test": "jest",
// "test:watch": "jest --watch",
// "test:coverage": "jest --coverage"
// }
Jasmine → Jest Syntax Differences
// Most syntax is identical — describe, it, beforeEach, afterEach, expect
// Key differences:
// JASMINE: createSpy / createSpyObj
const spy = jasmine.createSpy('methodName');
const obj = jasmine.createSpyObj('ServiceName', ['method1', 'method2']);
// JEST equivalent:
const spy = jest.fn();
const obj = {
method1: jest.fn(),
method2: jest.fn(),
};
// JASMINE: spy.and.returnValue()
spy.and.returnValue(value);
// JEST equivalent:
spy.mockReturnValue(value);
(spy as jest.Mock).mockReturnValue(value);
// JASMINE: spy.and.callFake()
spy.and.callFake(() => value);
// JEST equivalent:
spy.mockImplementation(() => value);
// JASMINE: toHaveBeenCalledWith
expect(spy).toHaveBeenCalledWith(arg1, arg2); // same in Jest ✓
// JASMINE: jasmine.objectContaining
expect(obj).toEqual(jasmine.objectContaining({ key: value }));
// JEST equivalent:
expect(obj).toEqual(expect.objectContaining({ key: value }));
// JEST-only: snapshot testing
it('should match post card snapshot', () => {
const { container } = render(PostCardComponent, { componentInputs: { post } });
expect(container.firstChild).toMatchSnapshot();
// Creates/compares a stored snapshot of the rendered HTML
});
--watch mode with intelligent test selection is the killer feature for TDD. When you save a source file, Jest only re-runs the test files that import the changed module — not the entire test suite. For a large BlogApp with 50 spec files, changing posts-api.service.ts only re-runs posts-api.service.spec.ts and any other spec that imports the service. This makes the TDD feedback loop nearly instant — sub-1-second from save to test result.BrowserAnimationsModule or NoopAnimationsModule, and Angular CDK’s OverlayContainer may need manual cleanup between tests. Test with your full component library after migration — not just simple component tests. If a subset of tests consistently fails after the Jest migration, check whether they depend on browser-specific APIs (window.matchMedia, IntersectionObserver) that jsdom doesn’t implement and need to be mocked globally in setup-jest.ts.Common Mistakes
Mistake 1 — Not updating tsconfig.spec.json types (jasmine vs jest)
❌ Wrong — types: ["jasmine"] after migrating to Jest; TypeScript errors on jest-specific matchers (toMatchSnapshot, jest.fn()).
✅ Correct — types: ["jest"] in tsconfig.spec.json; removes jasmine types; enables jest types.
Mistake 2 — Using jest.fn() where a typed spy is needed (no IntelliSense)
❌ Wrong — const service = { getPublished: jest.fn() }; no type safety; wrong argument types not caught.
✅ Correct — const service = { getPublished: jest.fn<ReturnType<PostsApiService['getPublished']>>() }; preserves type safety.