or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions-filtering.mdadvanced-features.mdeffect-creation.mdindex.mdmodule-setup.mdtesting.md

testing.mddocs/

0

# Testing Utilities

1

2

Testing support for NgRX Effects with mock providers and utilities that enable comprehensive unit testing of effects without requiring a full store setup.

3

4

## Capabilities

5

6

### provideMockActions Function

7

8

Creates mock Actions provider for testing effects in isolation, supporting both direct observables and factory functions.

9

10

```typescript { .api }

11

/**

12

* Creates mock Actions provider from an observable source

13

* @param source - Observable of actions to provide as mock Actions service

14

* @returns FactoryProvider for testing setup

15

*/

16

function provideMockActions(source: Observable<any>): FactoryProvider;

17

18

/**

19

* Creates mock Actions provider from a factory function

20

* @param factory - Function returning an observable of actions

21

* @returns FactoryProvider for testing setup

22

*/

23

function provideMockActions(factory: () => Observable<any>): FactoryProvider;

24

```

25

26

**Usage Examples:**

27

28

```typescript

29

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

30

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

31

import { Observable, of } from "rxjs";

32

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

33

34

describe('BookEffects', () => {

35

let actions$: Observable<Action>;

36

let effects: BookEffects;

37

let bookService: jasmine.SpyObj<BookService>;

38

39

beforeEach(() => {

40

const bookServiceSpy = jasmine.createSpyObj('BookService', ['getBooks', 'searchBooks']);

41

42

TestBed.configureTestingModule({

43

providers: [

44

BookEffects,

45

provideMockActions(() => actions$), // Factory function approach

46

{ provide: BookService, useValue: bookServiceSpy }

47

]

48

});

49

50

effects = TestBed.inject(BookEffects);

51

bookService = TestBed.inject(BookService) as jasmine.SpyObj<BookService>;

52

});

53

54

describe('loadBooks$', () => {

55

it('should return loadBooksSuccess action on successful API call', () => {

56

const books = [{ id: 1, title: 'Test Book' }];

57

const action = BookActions.loadBooks();

58

const completion = BookActions.loadBooksSuccess({ books });

59

60

// Arrange

61

actions$ = of(action);

62

bookService.getBooks.and.returnValue(of(books));

63

64

// Act & Assert

65

effects.loadBooks$.subscribe(result => {

66

expect(result).toEqual(completion);

67

});

68

});

69

70

it('should return loadBooksFailure action on API error', () => {

71

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

72

const action = BookActions.loadBooks();

73

const completion = BookActions.loadBooksFailure({ error: error.message });

74

75

// Arrange

76

actions$ = of(action);

77

bookService.getBooks.and.returnValue(throwError(error));

78

79

// Act & Assert

80

effects.loadBooks$.subscribe(result => {

81

expect(result).toEqual(completion);

82

});

83

});

84

});

85

});

86

87

// Alternative setup with direct observable

88

describe('UserEffects with direct observable', () => {

89

let effects: UserEffects;

90

let actions$: Observable<Action>;

91

92

beforeEach(() => {

93

actions$ = of(UserActions.loadUser({ id: 1 }));

94

95

TestBed.configureTestingModule({

96

providers: [

97

UserEffects,

98

provideMockActions(actions$), // Direct observable approach

99

// ... other providers

100

]

101

});

102

103

effects = TestBed.inject(UserEffects);

104

});

105

106

it('should handle user loading', () => {

107

effects.loadUser$.subscribe(action => {

108

expect(action.type).toBe('[User] Load User Success');

109

});

110

});

111

});

112

```

113

114

### Advanced Testing Patterns

115

116

**Testing Effects with Multiple Actions:**

117

118

```typescript

119

describe('SearchEffects', () => {

120

let actions$: Observable<Action>;

121

let effects: SearchEffects;

122

123

beforeEach(() => {

124

TestBed.configureTestingModule({

125

providers: [

126

SearchEffects,

127

provideMockActions(() => actions$),

128

// ... mock services

129

]

130

});

131

132

effects = TestBed.inject(SearchEffects);

133

});

134

135

it('should handle sequence of search actions', () => {

136

const actions = [

137

SearchActions.searchQuery({ query: 'test' }),

138

SearchActions.searchQuery({ query: 'updated' }),

139

SearchActions.clearSearch()

140

];

141

142

actions$ = from(actions);

143

144

// Test the effect handles the sequence correctly

145

const results: Action[] = [];

146

effects.search$.subscribe(action => results.push(action));

147

148

expect(results).toHaveLength(2); // Only search actions, not clear

149

});

150

});

151

```

152

153

**Testing Effects with State Dependencies:**

154

155

```typescript

156

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

157

158

describe('Effects with Store Dependencies', () => {

159

let actions$: Observable<Action>;

160

let effects: DataEffects;

161

let store: MockStore;

162

163

const initialState = {

164

user: { id: 1, name: 'Test User' },

165

settings: { theme: 'dark' }

166

};

167

168

beforeEach(() => {

169

TestBed.configureTestingModule({

170

providers: [

171

DataEffects,

172

provideMockActions(() => actions$),

173

provideMockStore({ initialState })

174

]

175

});

176

177

effects = TestBed.inject(DataEffects);

178

store = TestBed.inject(MockStore);

179

});

180

181

it('should use store state in effect', () => {

182

const action = DataActions.loadUserData();

183

actions$ = of(action);

184

185

// Mock selector return value

186

store.overrideSelector(selectCurrentUser, { id: 2, name: 'Updated User' });

187

188

effects.loadUserData$.subscribe(result => {

189

expect(result).toEqual(

190

DataActions.loadUserDataSuccess({

191

data: jasmine.objectContaining({ userId: 2 })

192

})

193

);

194

});

195

});

196

});

197

```

198

199

**Testing Non-Dispatching Effects:**

200

201

```typescript

202

describe('Non-dispatching Effects', () => {

203

let actions$: Observable<Action>;

204

let effects: LoggingEffects;

205

let consoleSpy: jasmine.Spy;

206

207

beforeEach(() => {

208

consoleSpy = spyOn(console, 'log');

209

210

TestBed.configureTestingModule({

211

providers: [

212

LoggingEffects,

213

provideMockActions(() => actions$)

214

]

215

});

216

217

effects = TestBed.inject(LoggingEffects);

218

});

219

220

it('should log actions without dispatching', () => {

221

const action = AppActions.userLogin({ username: 'testuser' });

222

actions$ = of(action);

223

224

// Subscribe to trigger the effect

225

effects.logActions$.subscribe();

226

227

expect(consoleSpy).toHaveBeenCalledWith('Action dispatched:', action);

228

});

229

});

230

```

231

232

**Testing Effects with Timing:**

233

234

```typescript

235

import { TestScheduler } from "rxjs/testing";

236

237

describe('Effects with Timing', () => {

238

let actions$: Observable<Action>;

239

let effects: TimingEffects;

240

let scheduler: TestScheduler;

241

242

beforeEach(() => {

243

scheduler = new TestScheduler((actual, expected) => {

244

expect(actual).toEqual(expected);

245

});

246

247

TestBed.configureTestingModule({

248

providers: [

249

TimingEffects,

250

provideMockActions(() => actions$)

251

]

252

});

253

254

effects = TestBed.inject(TimingEffects);

255

});

256

257

it('should debounce search actions', () => {

258

scheduler.run(({ hot, cold, expectObservable }) => {

259

const action1 = SearchActions.search({ query: 'test' });

260

const action2 = SearchActions.search({ query: 'test2' });

261

const expected = SearchActions.searchSuccess({ results: [] });

262

263

// Actions with timing

264

actions$ = hot('a-b 300ms c', {

265

a: action1,

266

b: action2,

267

c: action2

268

});

269

270

// Mock service response

271

searchService.search.and.returnValue(cold('100ms (a|)', { a: [] }));

272

273

// Only the last action should trigger the effect after debounce

274

expectObservable(effects.debouncedSearch$).toBe(

275

'400ms (a|)',

276

{ a: expected }

277

);

278

});

279

});

280

});

281

```

282

283

**Testing Functional Effects:**

284

285

```typescript

286

import { runInInjectionContext } from "@angular/core";

287

288

describe('Functional Effects', () => {

289

let actions$: Observable<Action>;

290

291

beforeEach(() => {

292

TestBed.configureTestingModule({

293

providers: [

294

provideMockActions(() => actions$),

295

// ... other providers needed by functional effects

296

]

297

});

298

});

299

300

it('should test functional effect', () => {

301

const action = DataActions.loadData();

302

const expectedAction = DataActions.loadDataSuccess({ data: [] });

303

304

actions$ = of(action);

305

306

// Test functional effect in injection context

307

runInInjectionContext(TestBed, () => {

308

const effect = loadDataEffect();

309

310

effect.subscribe(result => {

311

expect(result).toEqual(expectedAction);

312

});

313

});

314

});

315

});

316

```

317

318

**Testing Effects with Error Handling:**

319

320

```typescript

321

describe('Effects Error Handling', () => {

322

let actions$: Observable<Action>;

323

let effects: ErrorHandlingEffects;

324

325

beforeEach(() => {

326

TestBed.configureTestingModule({

327

providers: [

328

ErrorHandlingEffects,

329

provideMockActions(() => actions$),

330

// Mock error handler if needed

331

{ provide: EFFECTS_ERROR_HANDLER, useValue: customErrorHandler }

332

]

333

});

334

335

effects = TestBed.inject(ErrorHandlingEffects);

336

});

337

338

it('should handle service errors gracefully', () => {

339

const action = DataActions.loadData();

340

const error = new Error('Service unavailable');

341

const expectedAction = DataActions.loadDataFailure({ error: error.message });

342

343

actions$ = of(action);

344

dataService.loadData.and.returnValue(throwError(error));

345

346

effects.loadData$.subscribe(

347

result => expect(result).toEqual(expectedAction),

348

error => fail('Effect should handle errors gracefully')

349

);

350

});

351

});

352

```

353

354

### Testing Utilities Type Definitions

355

356

```typescript { .api }

357

interface FactoryProvider {

358

provide: any;

359

useFactory: Function;

360

deps?: any[];

361

}

362

363

interface TestBed {

364

configureTestingModule(moduleDef: TestModuleMetadata): typeof TestBed;

365

inject<T>(token: Type<T> | InjectionToken<T>): T;

366

}

367

368

interface TestModuleMetadata {

369

providers?: Provider[];

370

imports?: any[];

371

declarations?: any[];

372

}

373

```