Last active
February 23, 2025 02:51
-
-
Save ThomasBurleson/d2d62c8086e562621e597ba330aa38d6 to your computer and use it in GitHub Desktop.
Testing NgRx Facades with async/await.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { NgModule } from '@angular/core'; | |
import { TestBed } from '@angular/core/testing'; | |
import { readFirst } from '@nrwl/nx/testing'; | |
import { EffectsModule } from '@ngrx/effects'; | |
import { StoreModule, Store } from '@ngrx/store'; | |
import { NxModule } from '@nrwl/nx'; | |
/** | |
* NgRx feature slice for 'Cars' state | |
*/ | |
import { LoadCars, CarsLoaded } from './cars.actions'; | |
import { CarsEffects } from './cars.effects'; | |
import { CarsFacade } from './cars.facade'; | |
import { CarsState, Entity, initialState, carsReducer } from './cars.reducer'; | |
/** | |
* The full-app NgRx state only has a 'cars' feature slice | |
*/ | |
interface TestSchema { | |
'cars' : CarsState | |
} | |
describe('CarsFacade', () => { | |
let facade: CarsFacade; | |
let store: Store<TestSchema>; | |
let createCars; | |
beforeEach(() => { | |
createCars = ( id:string, name = '' ): Entity => ({ | |
id, | |
name: name ? `name-${id}` : id | |
}); | |
}); | |
describe('used in NgModule', async (done) => { | |
beforeEach(() => { | |
@NgModule({ | |
imports: [ | |
StoreModule.forFeature('cars', carsReducer, { initialState }), | |
EffectsModule.forFeature([CarsEffects]) | |
], | |
providers: [CarsFacade] | |
}) | |
class CustomFeatureModule {} | |
@NgModule({ | |
imports: [ | |
NxModule.forRoot(), | |
StoreModule.forRoot({}), | |
EffectsModule.forRoot([]), | |
CustomFeatureModule, | |
] | |
}) | |
class RootModule {} | |
TestBed.configureTestingModule({ imports: [RootModule] }); | |
store = TestBed.get(Store); | |
facade = TestBed.get(CarsFacade); | |
}); | |
/** | |
* The initially generated facade::loadAll() returns empty array | |
*/ | |
it('loadAll() should return empty list with loaded == true', async (done) => { | |
try { | |
let list = await readFirst(facade.allCars$); | |
let isLoaded = await readFirst(facade.loaded$); | |
expect(list.length).toBe(0); // initially empty | |
expect(isLoaded).toBe(false); // initially not loaded | |
facade.loadAll(); // In our case loadAll() always returns an empty array. | |
list = await readFirst(facade.allCars$); | |
isLoaded = await readFirst(facade.loaded$); | |
expect(isLoaded).toBe(true); // data load completed | |
expect(list.length).toBe(0); | |
done(); | |
} catch (err) { | |
done.fail(err); | |
} | |
}); | |
/** | |
* Use `CarsLoaded` to manually submit list for state management and | |
* test that our observable is properly streaming data changes. | |
*/ | |
it('allCars$ should return the current list', async (done) => { | |
try { | |
let list = await readFirst(facade.allCars$); | |
let isLoaded = await readFirst(facade.loaded$); | |
expect(list.length).toBe(0); | |
expect(isLoaded).toBe(false); | |
// Here we are testing our NgRx actions, reducers, and selectors. | |
// Simulate results of a loadAll() and add two (2) cars to our NgRx 'cars' state. | |
store.dispatch(new CarsLoaded([ | |
createCars('AAA'), | |
createCars('BBB') | |
])); | |
list = await readFirst(facade.allCars$); | |
isLoaded = await readFirst(facade.loaded$); | |
expect(list.length).toBe(2); | |
expect(isLoaded).toBe(true); | |
done(); | |
} catch (err) { | |
done.fail(err); | |
} | |
}); | |
}); | |
}); |
My solution was to make sure that both the application's rxjs and nrwl's rxjs were the same version. I adjusted the version in app's package.json to be the same as with the nrwl's one. Actually it was only a minor-level change, from 6.5.4 to 6.5.3.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Having the same issue:
TS2345: Argument of type 'import("/Users/..../node_modules/rxjs/internal/Observable").Observable<import("/Users/.../libs/bundles/src/lib/+state/bundles.reducer").Entity[]>' is not assignable to parameter of type 'import("/Users/..../node_modules/@nrwl/angular/node_modules/rxjs/internal/Observable").Observable<import("/Users/..../libs/bundles/src/lib/+state/bundles.reducer").Entity[]>'. The types of 'source.operator.call' are incompatible between these types. Type '(subscriber: import("/Users/.../node_modules/rxjs/internal/Subscriber").Subscriber<any>, source: any) => import("/Users/.../node_modules/rxjs/internal/types").TeardownLogic' is not assignable to type '(subscriber: import("/Users/.../node_modules/@nrwl/angular/node_modules/rxjs/internal/Subscriber").Subscriber<any>, source: any) => import("/Users/.../node_modules/@nrwl/angular/node_modules/rxjs/internal/types").TeardownLogic'. Types of parameters 'subscriber' and 'subscriber' are incompatible. Type 'import("/Users/.../node_modules/@nrwl/angular/node_modules/rxjs/internal/Subscriber").Subscriber<any>' is not assignable to type 'import("/Users/.../node_modules/rxjs/internal/Subscriber").Subscriber<any>'. Property 'isStopped' is protected but type 'Subscriber<T>' is not a class derived from 'Subscriber<T>'.