or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

action-creators.mdfeature-management.mdindex.mdmodule-configuration.mdreducer-creators.mdselectors.mdstandalone-providers.mdstore-service.mdtesting-utilities.md

testing-utilities.mddocs/

0

# Testing Utilities

1

2

Testing utilities provide comprehensive mock implementations and testing helpers for NgRx Store, enabling effective unit testing of components, services, and state management logic.

3

4

## Capabilities

5

6

### Provide Mock Store

7

8

Creates mock store providers for testing with TestBed configuration.

9

10

```typescript { .api }

11

/**

12

* Creates mock store providers for testing environments

13

* @param config - Mock store configuration with initial state and selectors

14

* @returns Provider array for TestBed configuration

15

*/

16

function provideMockStore<T = any>(

17

config?: MockStoreConfig<T>

18

): (ValueProvider | ExistingProvider | FactoryProvider)[];

19

20

/**

21

* Configuration for mock store setup

22

*/

23

interface MockStoreConfig<T> {

24

/** Initial state for mock store */

25

initialState?: T;

26

/** Mock selectors with predefined return values */

27

selectors?: MockSelector[];

28

}

29

```

30

31

**Usage Examples:**

32

33

```typescript

34

import { TestBed } from "@angular/core/testing";

35

import { provideMockStore, MockStore } from "@ngrx/store/testing";

36

import { selectUser, selectUserName } from "./user.selectors";

37

38

describe('UserComponent', () => {

39

let store: MockStore;

40

let component: UserComponent;

41

let fixture: ComponentFixture<UserComponent>;

42

43

beforeEach(async () => {

44

await TestBed.configureTestingModule({

45

imports: [UserComponent],

46

providers: [

47

provideMockStore({

48

initialState: {

49

user: {

50

entities: [

51

{ id: '1', name: 'John Doe', email: 'john@example.com' },

52

{ id: '2', name: 'Jane Smith', email: 'jane@example.com' }

53

],

54

selectedUserId: '1',

55

loading: false,

56

error: null

57

}

58

},

59

selectors: [

60

{ selector: selectUser, value: { id: '1', name: 'John Doe' } },

61

{ selector: selectUserName, value: 'John Doe' }

62

]

63

})

64

]

65

}).compileComponents();

66

67

store = TestBed.inject(MockStore);

68

fixture = TestBed.createComponent(UserComponent);

69

component = fixture.componentInstance;

70

});

71

72

it('should display user name', () => {

73

fixture.detectChanges();

74

expect(component.userName()).toBe('John Doe');

75

});

76

});

77

```

78

79

### Create Mock Store

80

81

Creates a standalone mock store instance without TestBed for service and effect testing.

82

83

```typescript { .api }

84

/**

85

* Creates mock store instance outside of TestBed

86

* @param config - Mock store configuration

87

* @returns MockStore instance for testing

88

*/

89

function createMockStore<T>(config?: MockStoreConfig<T>): MockStore<T>;

90

```

91

92

**Usage Examples:**

93

94

```typescript

95

import { createMockStore } from "@ngrx/store/testing";

96

import { UserEffects } from "./user.effects";

97

98

describe('UserEffects', () => {

99

let effects: UserEffects;

100

let store: MockStore;

101

let userService: jasmine.SpyObj<UserService>;

102

103

beforeEach(() => {

104

const userServiceSpy = jasmine.createSpyObj('UserService', ['loadUser', 'updateUser']);

105

106

store = createMockStore({

107

initialState: {

108

user: { entities: [], loading: false, error: null }

109

}

110

});

111

112

TestBed.configureTestingModule({

113

providers: [

114

UserEffects,

115

{ provide: Store, useValue: store },

116

{ provide: UserService, useValue: userServiceSpy }

117

]

118

});

119

120

effects = TestBed.inject(UserEffects);

121

userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;

122

});

123

124

it('should load user data', () => {

125

userService.loadUser.and.returnValue(of({ id: '1', name: 'Test User' }));

126

127

store.dispatch(loadUser({ id: '1' }));

128

129

expect(userService.loadUser).toHaveBeenCalledWith('1');

130

});

131

});

132

```

133

134

### Mock Store Class

135

136

Enhanced Store implementation with testing-specific methods for overriding selectors and managing state.

137

138

```typescript { .api }

139

/**

140

* Mock implementation of Store for testing

141

*/

142

class MockStore<T> extends Store<T> {

143

/**

144

* Override selector return value for testing

145

* @param selector - Selector to override

146

* @param value - Value to return from selector

147

*/

148

overrideSelector<Result>(

149

selector: MemoizedSelector<any, Result>,

150

value: Result

151

): void;

152

153

/**

154

* Refresh mock state and notify subscribers

155

*/

156

refreshState(): void;

157

158

/**

159

* Set mock state directly

160

* @param nextState - New state to set

161

*/

162

setState(nextState: T): void;

163

164

/**

165

* Reset all selector overrides

166

*/

167

resetSelectors(): void;

168

}

169

```

170

171

**Usage Examples:**

172

173

```typescript

174

import { MockStore } from "@ngrx/store/testing";

175

import { selectUserPreferences, selectTheme } from "./ui.selectors";

176

177

describe('UI Component Tests', () => {

178

let store: MockStore;

179

let component: ThemeToggleComponent;

180

181

beforeEach(() => {

182

TestBed.configureTestingModule({

183

providers: [provideMockStore()]

184

});

185

186

store = TestBed.inject(MockStore);

187

component = TestBed.inject(ThemeToggleComponent);

188

});

189

190

it('should handle theme changes', () => {

191

// Override selector return value

192

store.overrideSelector(selectTheme, 'dark');

193

store.refreshState();

194

195

expect(component.currentTheme()).toBe('dark');

196

197

// Change selector value during test

198

store.overrideSelector(selectTheme, 'light');

199

store.refreshState();

200

201

expect(component.currentTheme()).toBe('light');

202

});

203

204

it('should handle complex state changes', () => {

205

const mockPreferences = {

206

theme: 'auto',

207

language: 'en',

208

notifications: true

209

};

210

211

store.overrideSelector(selectUserPreferences, mockPreferences);

212

store.refreshState();

213

214

expect(component.preferences()).toEqual(mockPreferences);

215

216

// Direct state manipulation

217

store.setState({

218

...store.state(),

219

ui: {

220

preferences: { ...mockPreferences, theme: 'dark' }

221

}

222

});

223

224

expect(component.preferences().theme).toBe('dark');

225

});

226

});

227

```

228

229

### Mock Selector Interface

230

231

Configuration for mocking individual selectors with predefined return values.

232

233

```typescript { .api }

234

/**

235

* Configuration for mocking individual selectors

236

*/

237

interface MockSelector {

238

/** The selector to mock */

239

selector: MemoizedSelector<any, any> | string;

240

/** The value to return from the selector */

241

value: any;

242

}

243

```

244

245

**Usage Examples:**

246

247

```typescript

248

import { selectAllUsers, selectActiveUsers, selectUserCount } from "./user.selectors";

249

250

const mockSelectors: MockSelector[] = [

251

{

252

selector: selectAllUsers,

253

value: [

254

{ id: '1', name: 'John', active: true },

255

{ id: '2', name: 'Jane', active: false }

256

]

257

},

258

{

259

selector: selectActiveUsers,

260

value: [{ id: '1', name: 'John', active: true }]

261

},

262

{

263

selector: selectUserCount,

264

value: 2

265

}

266

];

267

268

TestBed.configureTestingModule({

269

providers: [

270

provideMockStore({

271

selectors: mockSelectors

272

})

273

]

274

});

275

```

276

277

## Testing Patterns

278

279

### Component Testing

280

281

```typescript

282

import { ComponentFixture, TestBed } from "@angular/core/testing";

283

import { MockStore } from "@ngrx/store/testing";

284

import { UserListComponent } from "./user-list.component";

285

286

describe('UserListComponent', () => {

287

let component: UserListComponent;

288

let fixture: ComponentFixture<UserListComponent>;

289

let store: MockStore;

290

291

const initialState = {

292

users: {

293

entities: [],

294

loading: false,

295

error: null,

296

filters: { search: '', role: 'all' }

297

}

298

};

299

300

beforeEach(async () => {

301

await TestBed.configureTestingModule({

302

imports: [UserListComponent],

303

providers: [provideMockStore({ initialState })]

304

}).compileComponents();

305

306

store = TestBed.inject(MockStore);

307

fixture = TestBed.createComponent(UserListComponent);

308

component = fixture.componentInstance;

309

});

310

311

it('should display loading state', () => {

312

store.setState({

313

...initialState,

314

users: { ...initialState.users, loading: true }

315

});

316

317

fixture.detectChanges();

318

319

expect(fixture.nativeElement.querySelector('.loading')).toBeTruthy();

320

});

321

322

it('should dispatch search action', () => {

323

spyOn(store, 'dispatch');

324

325

component.searchUsers('john');

326

327

expect(store.dispatch).toHaveBeenCalledWith(

328

searchUsers({ query: 'john' })

329

);

330

});

331

332

it('should handle error state', () => {

333

const errorMessage = 'Failed to load users';

334

335

store.setState({

336

...initialState,

337

users: { ...initialState.users, error: errorMessage }

338

});

339

340

fixture.detectChanges();

341

342

expect(fixture.nativeElement.textContent).toContain(errorMessage);

343

});

344

});

345

```

346

347

### Service Testing

348

349

```typescript

350

import { TestBed } from "@angular/core/testing";

351

import { createMockStore, MockStore } from "@ngrx/store/testing";

352

import { UserDataService } from "./user-data.service";

353

354

describe('UserDataService', () => {

355

let service: UserDataService;

356

let store: MockStore;

357

358

beforeEach(() => {

359

store = createMockStore({

360

initialState: {

361

users: { entities: [], selectedUserId: null }

362

}

363

});

364

365

TestBed.configureTestingModule({

366

providers: [

367

UserDataService,

368

{ provide: Store, useValue: store }

369

]

370

});

371

372

service = TestBed.inject(UserDataService);

373

});

374

375

it('should get current user', (done) => {

376

const mockUser = { id: '1', name: 'Test User' };

377

378

store.overrideSelector(service.selectCurrentUser, mockUser);

379

store.refreshState();

380

381

service.getCurrentUser().subscribe(user => {

382

expect(user).toEqual(mockUser);

383

done();

384

});

385

});

386

387

it('should load user by ID', () => {

388

spyOn(store, 'dispatch');

389

390

service.loadUser('123');

391

392

expect(store.dispatch).toHaveBeenCalledWith(

393

loadUser({ id: '123' })

394

);

395

});

396

});

397

```

398

399

### Effect Testing

400

401

```typescript

402

import { TestBed } from "@angular/core/testing";

403

import { provideMockActions } from "@ngrx/effects/testing";

404

import { Observable, of, throwError } from "rxjs";

405

import { MockStore, createMockStore } from "@ngrx/store/testing";

406

import { UserEffects } from "./user.effects";

407

408

describe('UserEffects', () => {

409

let actions$: Observable<any>;

410

let effects: UserEffects;

411

let store: MockStore;

412

let userService: jasmine.SpyObj<UserService>;

413

414

beforeEach(() => {

415

const userServiceSpy = jasmine.createSpyObj('UserService', ['loadUser']);

416

417

TestBed.configureTestingModule({

418

providers: [

419

UserEffects,

420

provideMockActions(() => actions$),

421

{ provide: UserService, useValue: userServiceSpy },

422

{ provide: Store, useValue: createMockStore() }

423

]

424

});

425

426

effects = TestBed.inject(UserEffects);

427

store = TestBed.inject(MockStore);

428

userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;

429

});

430

431

it('should load user successfully', () => {

432

const user = { id: '1', name: 'Test User' };

433

const action = loadUser({ id: '1' });

434

const outcome = loadUserSuccess({ user });

435

436

actions$ = of(action);

437

userService.loadUser.and.returnValue(of(user));

438

439

effects.loadUser$.subscribe(result => {

440

expect(result).toEqual(outcome);

441

});

442

});

443

444

it('should handle load user error', () => {

445

const error = new Error('Network error');

446

const action = loadUser({ id: '1' });

447

const outcome = loadUserFailure({ error: error.message });

448

449

actions$ = of(action);

450

userService.loadUser.and.returnValue(throwError(() => error));

451

452

effects.loadUser$.subscribe(result => {

453

expect(result).toEqual(outcome);

454

});

455

});

456

});

457

```

458

459

### Selector Testing

460

461

```typescript

462

import { selectUserSummary, selectFilteredUsers } from "./user.selectors";

463

464

describe('User Selectors', () => {

465

const mockState = {

466

users: {

467

entities: [

468

{ id: '1', name: 'John', role: 'admin', active: true },

469

{ id: '2', name: 'Jane', role: 'user', active: false },

470

{ id: '3', name: 'Bob', role: 'user', active: true }

471

],

472

filters: { role: 'user', active: true }

473

}

474

};

475

476

it('should select user summary', () => {

477

const result = selectUserSummary(mockState);

478

479

expect(result).toEqual({

480

total: 3,

481

active: 2,

482

byRole: { admin: 1, user: 2 }

483

});

484

});

485

486

it('should select filtered users', () => {

487

const result = selectFilteredUsers(mockState);

488

489

expect(result).toHaveLength(1);

490

expect(result[0]).toEqual({ id: '3', name: 'Bob', role: 'user', active: true });

491

});

492

493

it('should memoize selector results', () => {

494

const result1 = selectUserSummary(mockState);

495

const result2 = selectUserSummary(mockState);

496

497

// Same reference due to memoization

498

expect(result1).toBe(result2);

499

});

500

});

501

```

502

503

### Integration Testing

504

505

```typescript

506

import { ComponentFixture, TestBed } from "@angular/core/testing";

507

import { Store } from "@ngrx/store";

508

import { MockStore, provideMockStore } from "@ngrx/store/testing";

509

import { UserFeatureComponent } from "./user-feature.component";

510

511

describe('User Feature Integration', () => {

512

let component: UserFeatureComponent;

513

let fixture: ComponentFixture<UserFeatureComponent>;

514

let store: MockStore;

515

516

beforeEach(async () => {

517

await TestBed.configureTestingModule({

518

imports: [UserFeatureComponent],

519

providers: [

520

provideMockStore({

521

initialState: { users: { entities: [], loading: false } }

522

})

523

]

524

}).compileComponents();

525

526

store = TestBed.inject(MockStore);

527

fixture = TestBed.createComponent(UserFeatureComponent);

528

component = fixture.componentInstance;

529

});

530

531

it('should handle complete user workflow', () => {

532

spyOn(store, 'dispatch');

533

534

// Initial load

535

component.ngOnInit();

536

expect(store.dispatch).toHaveBeenCalledWith(loadUsers());

537

538

// Simulate loaded state

539

store.setState({

540

users: {

541

entities: [{ id: '1', name: 'John' }],

542

loading: false,

543

selectedUserId: null

544

}

545

});

546

fixture.detectChanges();

547

548

// User selection

549

component.selectUser('1');

550

expect(store.dispatch).toHaveBeenCalledWith(selectUser({ id: '1' }));

551

552

// User update

553

component.updateUser({ id: '1', name: 'John Updated' });

554

expect(store.dispatch).toHaveBeenCalledWith(

555

updateUser({ id: '1', changes: { name: 'John Updated' } })

556

);

557

});

558

});

559

```

560

561

## Additional Testing Components

562

563

### Mock Reducer Manager

564

565

Mock implementation of ReducerManager for testing dynamic reducer scenarios.

566

567

```typescript { .api }

568

/**

569

* Mock implementation of ReducerManager for testing

570

*/

571

class MockReducerManager extends BehaviorSubject<ActionReducer<any, any>> {

572

/** Mock current reducers */

573

get currentReducers(): ActionReducerMap<any, any>;

574

575

/** Mock methods for testing dynamic reducer management */

576

addFeature(feature: StoreFeature<any, any>): void;

577

addFeatures(features: StoreFeature<any, any>[]): void;

578

removeFeature(feature: StoreFeature<any, any>): void;

579

removeFeatures(features: StoreFeature<any, any>[]): void;

580

addReducer(key: string, reducer: ActionReducer<any, any>): void;

581

addReducers(reducers: ActionReducerMap<any, any>): void;

582

removeReducer(key: string): void;

583

removeReducers(featureKeys: string[]): void;

584

ngOnDestroy(): void;

585

}

586

```

587

588

**Usage Examples:**

589

590

```typescript

591

import { TestBed } from "@angular/core/testing";

592

import { MockReducerManager } from "@ngrx/store/testing";

593

import { ReducerManager } from "@ngrx/store";

594

595

describe('Dynamic Feature Service', () => {

596

let service: DynamicFeatureService;

597

let mockReducerManager: MockReducerManager;

598

599

beforeEach(() => {

600

TestBed.configureTestingModule({

601

providers: [

602

DynamicFeatureService,

603

{ provide: ReducerManager, useClass: MockReducerManager }

604

]

605

});

606

607

service = TestBed.inject(DynamicFeatureService);

608

mockReducerManager = TestBed.inject(ReducerManager) as MockReducerManager;

609

});

610

611

it('should add feature reducer', () => {

612

spyOn(mockReducerManager, 'addReducer');

613

614

service.loadUserFeature();

615

616

expect(mockReducerManager.addReducer).toHaveBeenCalledWith(

617

'users',

618

userReducer

619

);

620

});

621

});

622

```

623

624

### Mock State

625

626

Mock implementation of the core State service for testing.

627

628

```typescript { .api }

629

/**

630

* Mock implementation of State service for testing

631

*/

632

class MockState<T> extends BehaviorSubject<T> {

633

/** Mock state as Signal */

634

readonly state: Signal<T>;

635

636

/** Constructor takes initial state */

637

constructor(initialState?: T);

638

639

/** Mock lifecycle hook */

640

ngOnDestroy(): void;

641

}

642

```

643

644

**Usage Examples:**

645

646

```typescript

647

import { TestBed } from "@angular/core/testing";

648

import { MockState } from "@ngrx/store/testing";

649

import { State } from "@ngrx/store";

650

651

describe('State-dependent Service', () => {

652

let service: StateService;

653

let mockState: MockState<AppState>;

654

655

beforeEach(() => {

656

const initialState: AppState = {

657

user: { id: null, name: '' },

658

settings: { theme: 'light' }

659

};

660

661

TestBed.configureTestingModule({

662

providers: [

663

StateService,

664

{ provide: State, useValue: new MockState(initialState) }

665

]

666

});

667

668

service = TestBed.inject(StateService);

669

mockState = TestBed.inject(State) as MockState<AppState>;

670

});

671

672

it('should react to state changes', () => {

673

const newState: AppState = {

674

user: { id: '123', name: 'John' },

675

settings: { theme: 'dark' }

676

};

677

678

mockState.next(newState);

679

680

expect(service.currentUser()).toEqual(newState.user);

681

});

682

});

683

```

684

685

### Mock Selectors

686

687

Configuration for overriding selector behavior in tests.

688

689

```typescript { .api }

690

/**

691

* Mock selector configuration for testing

692

*/

693

interface MockSelector {

694

/** Selector function to override */

695

selector: any;

696

/** Value to return from selector */

697

value: any;

698

}

699

700

/** Token for injecting mock selectors */

701

const MOCK_SELECTORS: InjectionToken<MockSelector[]>;

702

```

703

704

**Usage Examples:**

705

706

```typescript

707

import { MockSelector, MOCK_SELECTORS } from "@ngrx/store/testing";

708

709

describe('Component with Custom Selectors', () => {

710

const mockSelectors: MockSelector[] = [

711

{ selector: selectUserRole, value: 'admin' },

712

{ selector: selectUserPermissions, value: ['read', 'write', 'delete'] },

713

{ selector: selectAppConfig, value: { version: '1.0.0', debug: true } }

714

];

715

716

beforeEach(async () => {

717

await TestBed.configureTestingModule({

718

imports: [AdminComponent],

719

providers: [

720

provideMockStore({

721

initialState: defaultState,

722

selectors: mockSelectors

723

})

724

]

725

}).compileComponents();

726

});

727

728

it('should show admin features', () => {

729

fixture.detectChanges();

730

731

expect(component.userRole()).toBe('admin');

732

expect(component.canDelete()).toBe(true);

733

});

734

});

735

```

736

737

### Manual Mock Store Creation

738

739

Alternative approach for creating mock stores without TestBed.

740

741

```typescript

742

import { MockStore, createMockStore } from "@ngrx/store/testing";

743

744

// Create standalone mock store

745

const mockStore = createMockStore({

746

initialState: { counter: 0 },

747

selectors: [

748

{ selector: selectCount, value: 42 }

749

]

750

});

751

752

// Use in service testing

753

const counterService = new CounterService(mockStore);

754

755

// Override selectors dynamically

756

mockStore.overrideSelector(selectCount, 100);

757

mockStore.refreshState();

758

759

// Verify dispatch calls

760

spyOn(mockStore, 'dispatch');

761

counterService.increment();

762

expect(mockStore.dispatch).toHaveBeenCalledWith(increment());

763

```

764

765

## Testing Patterns

766

767

### Component Testing with Signals

768

769

```typescript

770

describe('Signal-based Component', () => {

771

let component: UserProfileComponent;

772

let store: MockStore;

773

774

beforeEach(() => {

775

TestBed.configureTestingModule({

776

imports: [UserProfileComponent],

777

providers: [

778

provideMockStore({

779

initialState: { user: { name: 'John', email: 'john@test.com' } }

780

})

781

]

782

});

783

784

store = TestBed.inject(MockStore);

785

component = TestBed.createComponent(UserProfileComponent).componentInstance;

786

});

787

788

it('should update signal when state changes', () => {

789

expect(component.userName()).toBe('John');

790

791

store.setState({ user: { name: 'Jane', email: 'jane@test.com' } });

792

793

expect(component.userName()).toBe('Jane');

794

});

795

});

796

```

797

798

### Async Testing with Mock Store

799

800

```typescript

801

it('should handle async selectors', fakeAsync(() => {

802

const asyncData$ = store.select(selectAsyncData);

803

let result: any;

804

805

asyncData$.subscribe(data => result = data);

806

tick();

807

808

expect(result).toEqual(initialAsyncData);

809

810

store.overrideSelector(selectAsyncData, updatedAsyncData);

811

store.refreshState();

812

tick();

813

814

expect(result).toEqual(updatedAsyncData);

815

}));

816

```

817

818

## Best Practices

819

820

1. **Mock Configuration**: Use `provideMockStore` with meaningful initial state and selectors

821

2. **Selector Overrides**: Use `overrideSelector` to test different state scenarios

822

3. **Dispatch Verification**: Verify that components dispatch correct actions with proper payloads

823

4. **State Management**: Use `setState` for complex state changes during tests

824

5. **Effect Testing**: Test effects separately using `provideMockActions` and mock services

825

6. **Integration Testing**: Test complete workflows with realistic state transitions

826

7. **Signal Testing**: Test Signal-based components with proper state updates

827

8. **Mock Services**: Use MockReducerManager and MockState for testing internal NgRx services

828

9. **Async Testing**: Use `fakeAsync` and `tick()` for testing asynchronous state changes