Snapshot Testing Angular Components with Jest
Angular's default test runner is Jasmine with Karma. Many teams switch to Jest for faster test runs, better snapshot support, and a more modern testing experience. Once Jest is configured, snapshot testing Angular components works much like React — with a few Angular-specific considerations.
This guide covers setting up Jest for Angular, writing component snapshots, and handling Angular-specific patterns.
Setup: Jest for Angular
Installation
npm install --save-dev jest jest-preset-angular @types/jestConfiguration
// jest.config.js
module.exports = {
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|svg)$'
}
]
},
moduleNameMapper: {
'@app/(.*)': '<rootDir>/src/app/$1'
}
}// setup-jest.ts
import 'jest-preset-angular/setup-jest'// tsconfig.spec.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["jest"]
},
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
}Writing Component Snapshots
Basic Snapshot
// user-card.component.ts
@Component({
selector: 'app-user-card',
template: `
<div class="user-card">
<img [src]="avatar" [alt]="name">
<h2>{{ name }}</h2>
<p>{{ role }}</p>
</div>
`
})
export class UserCardComponent {
@Input() name: string
@Input() role: string
@Input() avatar: string
}// user-card.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { UserCardComponent } from './user-card.component'
describe('UserCardComponent', () => {
let component: UserCardComponent
let fixture: ComponentFixture<UserCardComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UserCardComponent]
}).compileComponents()
fixture = TestBed.createComponent(UserCardComponent)
component = fixture.componentInstance
})
it('renders with props', () => {
component.name = 'Alice'
component.role = 'Senior Engineer'
component.avatar = 'https://example.com/alice.jpg'
fixture.detectChanges()
expect(fixture.nativeElement).toMatchSnapshot()
})
})Standalone Components (Angular 14+)
// Angular 14+ standalone component
@Component({
standalone: true,
selector: 'app-badge',
template: `<span class="badge" [class]="variant">{{ label }}</span>`,
imports: [CommonModule]
})
export class BadgeComponent {
@Input() label: string
@Input() variant: 'success' | 'warning' | 'error' = 'success'
}
// Test
it('renders success badge', async () => {
await TestBed.configureTestingModule({
imports: [BadgeComponent] // import instead of declare for standalone
}).compileComponents()
const fixture = TestBed.createComponent(BadgeComponent)
fixture.componentInstance.label = 'Active'
fixture.componentInstance.variant = 'success'
fixture.detectChanges()
expect(fixture.nativeElement).toMatchSnapshot()
})Shallow vs Deep Rendering
Angular's TestBed renders child components by default (deep). For shallow rendering, declare child components as NO_ERRORS_SCHEMA or use stubs.
import { NO_ERRORS_SCHEMA } from '@angular/core'
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ParentComponent],
schemas: [NO_ERRORS_SCHEMA] // suppresses unknown element errors for child components
}).compileComponents()
})With NO_ERRORS_SCHEMA, child component selectors are rendered as unknown elements in the snapshot:
<app-parent>
<app-child-component></app-child-component> <!-- not fully rendered -->
</app-parent>This is the Angular equivalent of React's shallow() rendering.
Stub Child Components
For more explicit control, declare stub components:
@Component({ selector: 'app-header', template: '<div>HEADER STUB</div>' })
class MockHeaderComponent {}
@Component({ selector: 'app-footer', template: '<div>FOOTER STUB</div>' })
class MockFooterComponent {}
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AppComponent, MockHeaderComponent, MockFooterComponent]
}).compileComponents()
})Stubs give you explicit control over stub output, which makes snapshot diffs easier to read.
Testing Component States
describe('ButtonComponent', () => {
let component: ButtonComponent
let fixture: ComponentFixture<ButtonComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ButtonComponent]
}).compileComponents()
fixture = TestBed.createComponent(ButtonComponent)
component = fixture.componentInstance
})
it('default state', () => {
component.label = 'Submit'
fixture.detectChanges()
expect(fixture.nativeElement).toMatchSnapshot()
})
it('loading state', () => {
component.label = 'Submit'
component.loading = true
fixture.detectChanges()
expect(fixture.nativeElement).toMatchSnapshot()
})
it('disabled state', () => {
component.label = 'Submit'
component.disabled = true
fixture.detectChanges()
expect(fixture.nativeElement).toMatchSnapshot()
})
})Async Components and ngOnInit
For components that fetch data in ngOnInit:
import { of } from 'rxjs'
import { UserService } from './user.service'
it('renders after data loads', async () => {
const mockUserService = {
getUser: jest.fn().mockReturnValue(of({ name: 'Alice', email: 'alice@example.com' }))
}
await TestBed.configureTestingModule({
declarations: [UserProfileComponent],
providers: [
{ provide: UserService, useValue: mockUserService }
]
}).compileComponents()
const fixture = TestBed.createComponent(UserProfileComponent)
fixture.detectChanges() // triggers ngOnInit
await fixture.whenStable() // wait for async operations
fixture.detectChanges() // update view after data arrives
expect(fixture.nativeElement).toMatchSnapshot()
})Handling Dynamic Data
Freeze Dates
import MockDate from 'mockdate'
beforeEach(() => MockDate.set('2024-01-15T12:00:00Z'))
afterEach(() => MockDate.reset())
it('shows creation date', () => {
component.item = { title: 'Test', createdAt: new Date() }
fixture.detectChanges()
expect(fixture.nativeElement).toMatchSnapshot()
})Mock Random/Generated Values
If the component generates IDs or uses Math.random():
beforeEach(() => {
jest.spyOn(Math, 'random').mockReturnValue(0.5)
})
afterEach(() => {
jest.restoreAllMocks()
})Snapshot the nativeElement vs innerHTML
Both approaches work, with different output:
// Option 1: nativeElement — includes the component host element
expect(fixture.nativeElement).toMatchSnapshot()
// Output: <app-user-card><div class="user-card">...</div></app-user-card>
// Option 2: innerHTML — just the component's template content
expect(fixture.nativeElement.innerHTML).toMatchSnapshot()
// Output: <div class="user-card">...</div>
// Option 3: specific element
const card = fixture.nativeElement.querySelector('.user-card')
expect(card).toMatchSnapshot()innerHTML is often cleaner for component library testing — you don't see the host element wrapper.
Inline Snapshots for Small Components
it('renders badge', () => {
component.label = 'New'
component.variant = 'success'
fixture.detectChanges()
expect(fixture.nativeElement.innerHTML.trim()).toMatchInlineSnapshot(`
"<span class="badge badge--success">New</span>"
`)
})Updating Snapshots
# Update all snapshots
npx jest --updateSnapshot
# Update specific test file
npx jest user-card.component.spec.ts --updateSnapshot
# Interactive mode — press u to update
npx jest --watchCommon Issues
Zone.js and async: Angular uses Zone.js for change detection. In tests, always call fixture.detectChanges() after changing inputs, and await fixture.whenStable() after async operations.
Template compilation: If using jest-preset-angular, HTML templates are compiled as part of the Jest transform. If templates aren't resolving, check the transform config covers .html files.
Change detection: Angular's default change detection is Default (automatic). If your component uses OnPush, you need to manually mark it as dirty:
// For OnPush components
fixture.changeDetectorRef.markForCheck()
fixture.detectChanges()Summary
Snapshot testing Angular components with Jest requires:
jest-preset-angularfor Angular template supportTestBed.configureTestingModulefor each testfixture.detectChanges()to trigger renderingNO_ERRORS_SCHEMAor component stubs for shallow rendering
Once set up, the snapshot API is identical to React — toMatchSnapshot(), toMatchInlineSnapshot(), and --updateSnapshot work the same way. The Angular-specific part is the setup and the change detection lifecycle.