4.4 KiB
paths
| paths | ||
|---|---|---|
|
Angular Testing
This file extends common/testing.md with Angular specific content.
Test Runner
Use the test runner configured by the project. Check angular.json and package.json; Angular projects commonly use Vitest, Jest, or Jasmine + Karma.
ng test # watch mode
ng test --no-watch # CI mode
TestBed Setup
For standalone components, import the component directly. Call compileComponents() for components with external templates.
describe('UserCardComponent', () => {
let fixture: ComponentFixture<UserCardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserCardComponent],
}).compileComponents();
fixture = TestBed.createComponent(UserCardComponent);
});
});
Signal Inputs
Set signal-based inputs via fixture.componentRef.setInput():
fixture.componentRef.setInput('user', mockUser);
fixture.detectChanges();
Component Harnesses
Prefer Angular CDK component harnesses over direct DOM queries for UI interaction. Harnesses are more resilient to markup changes.
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';
let loader: HarnessLoader;
beforeEach(() => {
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('triggers save on button click', async () => {
const button = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));
await button.click();
expect(saveSpy).toHaveBeenCalled();
});
Router Testing
Use RouterTestingHarness for components that depend on the router:
import { RouterTestingHarness } from '@angular/router/testing';
it('renders user on navigation', async () => {
const harness = await RouterTestingHarness.create();
const component = await harness.navigateByUrl('/users/1', UserDetailComponent);
expect(component.userId()).toBe('1');
});
Async Testing
Use fakeAsync + tick for controlled async. Use waitForAsync for real async with fixture.whenStable().
it('loads user after delay', fakeAsync(() => {
const service = TestBed.inject(UserService);
vi.spyOn(service, 'getUser').mockReturnValue(of(mockUser));
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.name').textContent).toBe(mockUser.name);
}));
HTTP Testing
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { HttpTestingController } from '@angular/common/http/testing';
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting()],
});
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => httpMock.verify());
Service Testing
Inject services directly without a component fixture:
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting()],
});
service = TestBed.inject(UserService);
});
});
What to Test
- Services: All public methods, error paths, HTTP interactions
- Components: Input/output bindings, rendered output for key states, user interactions via harnesses
- Pipes: Pure transformation — plain unit tests, no TestBed needed
- Guards/Resolvers: Return values for allowed and denied states using
RouterTestingHarness
E2E Testing
Use the project's configured E2E framework, such as Cypress or Playwright, for critical user flows.
describe('Login flow', () => {
it('redirects to dashboard on valid credentials', () => {
cy.visit('/login');
cy.get('[data-cy=email]').type('user@example.com');
cy.get('[data-cy=password]').type('password123');
cy.get('[data-cy=submit]').click();
cy.url().should('include', '/dashboard');
});
});
- Add
data-cyattributes to interactive elements for stable selectors - Do not rely on CSS classes or text content for selectors in E2E tests
Coverage
Target ≥80% for services and pipes. Components: test behaviour, not implementation details.
Skill Reference
See skill: angular-developer for comprehensive testing patterns, harness usage, and async best practices.