Testing is a critical part of building robust Angular applications. This guide covers strategies and best practices for testing applications that use Angular Material components.
Testing Philosophy
Test Behavior, Not Implementation Focus on how users interact with your application, not internal details
Use Component Harnesses Leverage Material’s harnesses to insulate tests from implementation changes
Write Maintainable Tests Clear, readable tests that serve as documentation
Balance Coverage Mix unit, integration, and e2e tests appropriately
Test Types
Unit Tests
Integration Tests
E2E Tests
Test individual components or services in isolation. When to Use:
Testing component logic
Service functionality
Pipes and directives
Utility functions
Tools:
Jasmine/Jest
Angular TestBed
Component harnesses
Test how components work together. When to Use:
Form workflows
Component interactions
Routing scenarios
Complex UI behaviors
Tools:
TestBed with multiple components
Harnesses for Material components
Router testing modules
Test the entire application from a user’s perspective. When to Use:
Critical user journeys
Multi-page workflows
Authentication flows
Production-like scenarios
Tools:
Selenium WebDriver
Playwright
Cypress
Material harnesses (Selenium)
Unit Testing with Material Components
Basic Setup
import { ComponentFixture , TestBed } from '@angular/core/testing' ;
import { HarnessLoader } from '@angular/cdk/testing' ;
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed' ;
import { MatButtonModule } from '@angular/material/button' ;
import { MatButtonHarness } from '@angular/material/button/testing' ;
describe ( 'MyComponent' , () => {
let component : MyComponent ;
let fixture : ComponentFixture < MyComponent >;
let loader : HarnessLoader ;
beforeEach ( async () => {
await TestBed . configureTestingModule ({
imports: [ MatButtonModule ],
declarations: [ MyComponent ]
}). compileComponents ();
fixture = TestBed . createComponent ( MyComponent );
component = fixture . componentInstance ;
loader = TestbedHarnessEnvironment . loader ( fixture );
});
it ( 'should create' , () => {
expect ( component ). toBeTruthy ();
});
});
import { MatSelectHarness } from '@angular/material/select/testing' ;
it ( 'should select an option' , async () => {
const select = await loader . getHarness ( MatSelectHarness );
await select . open ();
const options = await select . getOptions ();
await options [ 1 ]. click ();
expect ( await select . getValueText ()). toBe ( 'Option 2' );
expect ( component . selectedValue ). toBe ( 'option2' );
});
it ( 'should filter options by text' , async () => {
const select = await loader . getHarness ( MatSelectHarness );
await select . open ();
const option = await select . getOption ({ text: 'Specific Option' });
await option . click ();
expect ( component . selectedValue ). toBe ( 'specific' );
});
import { MatCheckboxHarness } from '@angular/material/checkbox/testing' ;
import { MatRadioGroupHarness } from '@angular/material/radio/testing' ;
it ( 'should toggle checkbox' , async () => {
const checkbox = await loader . getHarness (
MatCheckboxHarness . with ({ label: 'Accept Terms' })
);
expect ( await checkbox . isChecked ()). toBe ( false );
await checkbox . check ();
expect ( await checkbox . isChecked ()). toBe ( true );
expect ( component . termsAccepted ). toBe ( true );
});
it ( 'should select radio option' , async () => {
const radioGroup = await loader . getHarness ( MatRadioGroupHarness );
const radios = await radioGroup . getRadioButtons ();
await radios [ 1 ]. check ();
expect ( await radioGroup . getCheckedValue ()). toBe ( 'option2' );
});
Testing Dialogs
import { MatDialogHarness } from '@angular/material/dialog/testing' ;
it ( 'should open and close dialog' , async () => {
// Get document root loader for overlays
const documentRootLoader = TestbedHarnessEnvironment . documentRootLoader ( fixture );
// Trigger dialog open
const openButton = await loader . getHarness (
MatButtonHarness . with ({ text: 'Open Dialog' })
);
await openButton . click ();
// Get dialog harness
const dialog = await documentRootLoader . getHarness ( MatDialogHarness );
expect ( await dialog . getTitleText ()). toBe ( 'Confirm Action' );
expect ( await dialog . getContentText ()). toContain ( 'Are you sure' );
// Close dialog
await dialog . close ();
// Verify dialog is closed
const dialogs = await documentRootLoader . getAllHarnesses ( MatDialogHarness );
expect ( dialogs . length ). toBe ( 0 );
});
it ( 'should pass data to dialog' , async () => {
component . openDialog ({ message: 'Test Message' });
fixture . detectChanges ();
const documentRootLoader = TestbedHarnessEnvironment . documentRootLoader ( fixture );
const dialog = await documentRootLoader . getHarness ( MatDialogHarness );
expect ( await dialog . getContentText ()). toContain ( 'Test Message' );
});
Testing Tables
import { MatTableHarness } from '@angular/material/table/testing' ;
it ( 'should display table data' , async () => {
const table = await loader . getHarness ( MatTableHarness );
const rows = await table . getRows ();
expect ( rows . length ). toBe ( 3 );
const firstRowCells = await rows [ 0 ]. getCells ();
const firstRowText = await parallel (() =>
firstRowCells . map ( cell => cell . getText ())
);
expect ( firstRowText ). toEqual ([ 'John' , 'Doe' , '30' ]);
});
it ( 'should sort table' , async () => {
const table = await loader . getHarness ( MatTableHarness );
const headers = await table . getHeaderRows ();
const nameHeader = await headers [ 0 ]. getCells ({ columnName: 'name' });
await nameHeader [ 0 ]. click (); // Sort ascending
let rows = await table . getRows ();
let firstCell = await rows [ 0 ]. getCells ();
expect ( await firstCell [ 0 ]. getText ()). toBe ( 'Alice' );
await nameHeader [ 0 ]. click (); // Sort descending
rows = await table . getRows ();
firstCell = await rows [ 0 ]. getCells ();
expect ( await firstCell [ 0 ]. getText ()). toBe ( 'Zoe' );
});
Best Practices
Use harnesses for Material components
Always prefer component harnesses over direct DOM queries: // ❌ Bad - brittle, depends on internals
const button = fixture . nativeElement . querySelector ( '.mat-mdc-button' );
button . click ();
// ✅ Good - robust, maintainable
const button = await loader . getHarness ( MatButtonHarness );
await button . click ();
Filter semantically
Use meaningful properties to identify components: // ❌ Bad - implementation detail
MatButtonHarness . with ({ selector: '.submit-btn' })
// ✅ Good - semantic meaning
MatButtonHarness . with ({ text: 'Submit' })
MatInputHarness . with ({ placeholder: 'Enter email' })
Test user behavior
Focus on what users do, not implementation: // ❌ Bad - testing implementation
it ( 'should set isOpen to true' , () => {
component . isOpen = true ;
expect ( component . isOpen ). toBe ( true );
});
// ✅ Good - testing behavior
it ( 'should show menu when button clicked' , async () => {
const menu = await loader . getHarness ( MatMenuHarness );
await menu . open ();
expect ( await menu . isOpen ()). toBe ( true );
});
Use async/await consistently
All harness operations are async: // ❌ Bad - missing await
const button = loader . getHarness ( MatButtonHarness );
button . click ();
// ✅ Good - proper async handling
const button = await loader . getHarness ( MatButtonHarness );
await button . click ();
Scope harness loaders
Use child loaders for specificity: // Get loader for specific section
const headerLoader = await loader . getChildLoader ( 'header' );
const headerButton = await headerLoader . getHarness ( MatButtonHarness );
const footerLoader = await loader . getChildLoader ( 'footer' );
const footerButton = await footerLoader . getHarness ( MatButtonHarness );
Testing Accessibility
Always test that your Material components are accessible!
it ( 'should have proper ARIA attributes' , async () => {
const button = await loader . getHarness ( MatButtonHarness );
const host = await button . host ();
expect ( await host . getAttribute ( 'aria-label' )). toBe ( 'Submit form' );
expect ( await host . getAttribute ( 'role' )). toBe ( 'button' );
});
it ( 'should manage focus properly' , async () => {
const input = await loader . getHarness ( MatInputHarness );
await input . focus ();
expect ( await input . isFocused ()). toBe ( true );
await input . blur ();
expect ( await input . isFocused ()). toBe ( false );
});
it ( 'should announce changes to screen readers' , async () => {
const snackBar = await documentRootLoader . getHarness ( MatSnackBarHarness );
expect ( await snackBar . getRole ()). toBe ( 'alert' );
expect ( await snackBar . getMessage ()). toBe ( 'Changes saved' );
});
it ( 'should render large list efficiently' , async () => {
const startTime = performance . now ();
component . items = Array . from ({ length: 1000 }, ( _ , i ) => ({
id: i ,
name: `Item ${ i } `
}));
fixture . detectChanges ();
await fixture . whenStable ();
const endTime = performance . now ();
const renderTime = endTime - startTime ;
expect ( renderTime ). toBeLessThan ( 1000 ); // Should render in <1s
});
Common Pitfalls
Forgetting to await harness operations
// ❌ This will fail!
const button = loader . getHarness ( MatButtonHarness );
button . click (); // Error: button is a Promise!
// ✅ Correct
const button = await loader . getHarness ( MatButtonHarness );
await button . click ();
Using wrong loader for overlays
// ❌ Wrong - overlays are attached to document body
const dialog = await loader . getHarness ( MatDialogHarness );
// ✅ Correct - use document root loader
const documentRootLoader = TestbedHarnessEnvironment . documentRootLoader ( fixture );
const dialog = await documentRootLoader . getHarness ( MatDialogHarness );
Not waiting for animations
// ❌ May fail if animations aren't complete
await dialog . close ();
expect ( component . dialogClosed ). toBe ( true );
// ✅ Harnesses wait for animations automatically
await dialog . close ();
// Dialog close animation completes before promise resolves
expect ( component . dialogClosed ). toBe ( true );
Testing Checklist
Use component harnesses for all Material components
Test user interactions, not implementation details
Verify accessibility attributes and behavior
Test form validation and error states
Test responsive behavior at different viewports
Test keyboard navigation and shortcuts
Test loading and error states
Use semantic filters (text, labels) over CSS selectors
Handle asynchronous operations with async/await
Test both happy path and edge cases
Resources
Component Harnesses Complete guide to using harnesses
Angular Testing Official Angular testing guide
CDK Testing CDK test harnesses documentation
Accessibility Testing Web accessibility testing resources