Write Angular Unit Tests
Analyze $ARGUMENTS and write comprehensive unit tests.
Steps
- Read the source file at the provided path.
- Read the existing
.spec.tsfile if one exists. - Read the project's
CLAUDE.mdfor testing conventions, test runner (Jest or Jasmine), and state management approach. - Identify what needs to be tested based on the type of Angular artifact.
- Write or update the
.spec.tsfile.
What to Test by Type
Components
- Component creation
- Input binding (set inputs, verify template renders correctly)
- Output emission (trigger actions, verify output emits expected values)
- Signal and computed state (verify derived state updates correctly)
- Template rendering with
@if,@for,@switchbranches - User interactions (click, input, form submission)
- Conditional template elements (all branches)
- Injected service interactions (mock services, verify calls)
- Page components: verify service methods are called, verify data flows to child views
- View components: verify inputs render correctly, verify outputs emit on user action
Services (Signal-Based)
- Public methods (call method, verify signal/state changes)
- HTTP calls (mock with
HttpTestingController, test success and error) - Signal state transitions (initial -> loading -> loaded/error)
- Computed properties (verify derived values)
- Edge cases (empty data, null values, error responses)
NgRx State
- Reducers: test as pure functions — pass initial state + action, assert new state
- Selectors: test with
.projector()or by composing mock state objects - Effects: test with
provideMockActions, mock API services, verify dispatched actions for success and error paths
Pipes
- Transform with typical input
- Transform with edge cases (null, undefined, empty string)
- Transform with various argument combinations
Directives
- Directive applied to host element
- Input changes reflected in DOM
- Event handling
Testing Patterns
Component test setup
typescript
describe('MyComponent', () => {
let fixture: ComponentFixture<MyComponent>;
let component: MyComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MyComponent],
providers: [
// For Jest: use jest.fn() or manual mock objects
{ provide: MyService, useValue: { loadItems: jest.fn(), items: signal([]), loading: signal(false) } }
// For Jasmine: use jasmine.createSpyObj
// { provide: MyService, useValue: jasmine.createSpyObj('MyService', ['loadItems'], { items: signal([]), loading: signal(false) }) }
]
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
});
});Service test setup with HTTP
typescript
describe('MyService', () => {
let service: MyService;
let httpTesting: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting()]
});
service = TestBed.inject(MyService);
httpTesting = TestBed.inject(HttpTestingController);
});
afterEach(() => httpTesting.verify());
});Setting component inputs in tests
typescript
// For signal inputs, use componentRef.setInput
fixture.componentRef.setInput('user', { id: '1', name: 'Test' });
fixture.detectChanges();NgRx reducer test
typescript
describe('MyReducer', () => {
it('should return initial state', () => {
const state = myReducer(undefined, { type: 'unknown' });
expect(state).toEqual(initialState);
});
it('should handle loadItems', () => {
const state = myReducer(initialState, MyActions.loadItems());
expect(state.loading).toBe(true);
});
it('should handle loadItemsSuccess', () => {
const items = [{ id: '1', name: 'Test' }];
const state = myReducer(initialState, MyActions.loadItemsSuccess({ items }));
expect(selectAll(state)).toEqual(items);
expect(state.loading).toBe(false);
});
it('should handle loadItemsFailure', () => {
const state = myReducer(initialState, MyActions.loadItemsFailure({ error: 'fail' }));
expect(state.error).toBe('fail');
expect(state.loading).toBe(false);
});
});NgRx selector test
typescript
describe('MySelectors', () => {
const mockState: MyState = {
ids: ['1'],
entities: { '1': { id: '1', name: 'Test' } },
loading: false,
error: null
};
it('should select all items', () => {
const result = MySelectors.selectAllItems.projector(mockState);
expect(result).toEqual([{ id: '1', name: 'Test' }]);
});
it('should select loading', () => {
const result = MySelectors.selectLoading.projector(mockState);
expect(result).toBe(false);
});
});NgRx effect test (Jest)
typescript
describe('MyEffects', () => {
let effects: MyEffects;
let actions$: Observable<Action>;
let api: jest.Mocked<MyApiService>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MyEffects,
provideMockActions(() => actions$),
{ provide: MyApiService, useValue: { getItems: jest.fn() } }
]
});
effects = TestBed.inject(MyEffects);
api = TestBed.inject(MyApiService) as jest.Mocked<MyApiService>;
});
it('should dispatch loadItemsSuccess on success', (done) => {
const items = [{ id: '1', name: 'Test' }];
api.getItems.mockReturnValue(of(items));
actions$ = of(MyActions.loadItems());
effects.loadItems$.subscribe(action => {
expect(action).toEqual(MyActions.loadItemsSuccess({ items }));
done();
});
});
it('should dispatch loadItemsFailure on error', (done) => {
api.getItems.mockReturnValue(throwError(() => new Error('fail')));
actions$ = of(MyActions.loadItems());
effects.loadItems$.pipe(take(1)).subscribe(action => {
expect(action).toEqual(MyActions.loadItemsFailure({ error: 'fail' }));
done();
});
});
});NgRx effect test (Jasmine)
typescript
describe('MyEffects', () => {
let effects: MyEffects;
let actions$: Observable<Action>;
let api: jasmine.SpyObj<MyApiService>;
beforeEach(() => {
api = jasmine.createSpyObj('MyApiService', ['getItems']);
TestBed.configureTestingModule({
providers: [
MyEffects,
provideMockActions(() => actions$),
{ provide: MyApiService, useValue: api }
]
});
effects = TestBed.inject(MyEffects);
});
it('should dispatch loadItemsSuccess on success', (done) => {
const items = [{ id: '1', name: 'Test' }];
api.getItems.and.returnValue(of(items));
actions$ = of(MyActions.loadItems());
effects.loadItems$.subscribe(action => {
expect(action).toEqual(MyActions.loadItemsSuccess({ items }));
done();
});
});
});Rules
- Place the test file next to the source file (e.g.,
my.component.spec.tsbesidemy.component.ts) - Mock all injected services; do not test service internals from component tests
- Use
jest.fn()(Jest) orjasmine.createSpyObj(Jasmine) with signal properties for service mocks - Use
fixture.componentRef.setInput()to set signal inputs in tests - Call
fixture.detectChanges()after state changes to update the template - Test observable and signal behavior, not implementation details
- Avoid
anytype in tests; use proper types for mocks - Do not add unnecessary
async/awaitunless testing async behavior - Keep test descriptions specific: "should display user name when user input is provided"
- For NgRx reducers: test every
on()handler with the corresponding action - For NgRx effects: test both success and error paths; verify the correct action is dispatched
Output
After writing tests, summarize:
- Test file location
- Number of test cases written
- What behaviors are covered
- Any edge cases or scenarios that are difficult to test automatically