angular-testing
Angular Testing with Jest
Test Angular v21+ applications with Jest and @testing-library/angular, prioritizing user-centric testing over implementation details.
Core Principles
- User-Centric Testing: Simulate real user behavior using
@testing-library/angular. Avoid testing implementation details or private state. - Modern Angular: Follow Angular v20+ standards (Standalone components, Signals,
@if/@forcontrol flow). - Accessibility: Use semantic queries (getByRole, getByLabelText) that promote accessible markup.
Framework & Syntax
Jest Globals
Always use Jest globals:
describe,it,expect,jest(e.g.,jest.fn(),jest.spyOn())- Never use
jasmine,spyOn, ordone()
Testing Library DOM Matchers
Use @testing-library/jest-dom matchers for better assertions:
// Good - Use jest-dom matchers
expect(button).toHaveClass('primary');
expect(text).toBeVisible();
expect(element).toHaveTextContent('Hello');
expect(button).toBeDisabled();
// Bad - Avoid direct DOM manipulation
expect(element.classList.contains('name')).toBe(true);
expect(element.style.display).toBe('none');
Test Structure (AAA Pattern)
Strict AAA Guidelines
Structure every test into Arrange, Act, and Assert blocks:
- Leave exactly one empty line between Arrange/Act/Assert blocks
- Do NOT include
// Arrangeor// Actor// Assertcomments - Use meaningful test titles with active voice
Test Naming
- Use active voice, describe what the test does
- Avoid "should" phrasing
// Bad - Generic "should" title
it('should handle submit', () => { });
// Good - Descriptive and specific
it('prevents submission when the email field is invalid', () => { });
// Good - Clear behavior description
it('updates the profile when the save button is clicked', () => { });
Component Testing Strategy
Isolation
Each it block must be self-contained. Use beforeEach only for setup truly shared across all tests.
Query Selection
Use Testing Library queries in order of preference:
screen.getByRole()- Most accessible and semanticscreen.getByLabelText()- For form elementsscreen.getByText()- For static text contentscreen.getByTestId()- Last resort only
User Interactions
Use userEvent from @testing-library/user-event for realistic interactions instead of native DOM events.
Public API Testing
Test only public fields and methods. Test protected or private logic only through public triggers (user interactions, input changes, public method calls).
Business Logic Focus
Do not test Angular's built-in directives (like @if, @for). Test the component's unique inputs, outputs, and business logic.
Mocking & Async
Effective Mocking
Use jest.spyOn() or jest.fn() to isolate dependencies. Avoid importing heavy modules; mock services and APIs at the boundary.
Async Handling
Use async/await for all promises returned by queries or events. Never leave a promise unhandled.
Signals
Update signal state via component.mySignal.set() and verify the UI updates automatically.
HTTP
Use HttpTestingController and provideHttpClientTesting() for service tests.
Best Practices Summary
- User-Centric: Test what users see and interact with
- AAA Pattern: Arrange, Act, Assert with clear separation
- Meaningful Names: Active voice, specific behavior
- Isolation: Each test is self-contained
- Accessibility: Use semantic queries (getByRole, getByLabelText)
- Public API Only: Don't test private implementation
- Async Handling: Always await promises and user events
- Mock at Boundaries: Mock services, not internal logic
- Business Logic Focus: Don't test Angular's built-in directives
- DOM Matchers: Use jest-dom for readable assertions
For complete usage examples and patterns, see references/testing-jest-patterns.md.
More from zard-ui/zardui
angular-signals
Implement signal-based reactive state management in Angular v20+. Use for creating reactive state with signal(), derived state with computed(), dependent state with linkedSignal(), and side effects with effect(). Triggers on state management questions, converting from BehaviorSubject/Observable patterns to signals, or implementing reactive data flows.
23angular-http
Implement HTTP data fetching in Angular v20+ using resource(), httpResource(), and HttpClient. Use for API calls, data loading with signals, request/response handling, and interceptors. Triggers on data fetching, API integration, loading states, error handling, or converting Observable-based HTTP to signal-based patterns.
22angular-di
Implement dependency injection in Angular v20+ using inject(), injection tokens, and provider configuration. Use for service architecture, providing dependencies at different levels, creating injectable tokens, and managing singleton vs scoped services. Triggers on service creation, configuring providers, using injection tokens, or understanding DI hierarchy.
22angular-routing
Implement routing in Angular v20+ applications with lazy loading, functional guards, resolvers, and route parameters. Use for navigation setup, protected routes, route-based data loading, and nested routing. Triggers on route configuration, adding authentication guards, implementing lazy loading, or reading route parameters with signals.
21angular-tooling
Use Angular CLI and development tools effectively in Angular v20+ projects. Use for project setup, code generation, building, testing, and configuration. Triggers on creating new projects, generating components/services/modules, configuring builds, running tests, or optimizing production builds.
21angular-forms
Build signal-based forms in Angular v21+ using the new Signal Forms API. Use for form creation with automatic two-way binding, schema-based validation, field state management, and dynamic forms. Triggers on form implementation, adding validation, creating multi-step forms, or building forms with conditional fields. Signal Forms are experimental but recommended for new Angular projects.
21