Skip to content

Instantly share code, notes, and snippets.

@ThomasBurleson
Last active April 21, 2023 02:32
Show Gist options
  • Save ThomasBurleson/d2d62c8086e562621e597ba330aa38d6 to your computer and use it in GitHub Desktop.
Save ThomasBurleson/d2d62c8086e562621e597ba330aa38d6 to your computer and use it in GitHub Desktop.
Testing NgRx Facades with async/await.
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);
}
});
});
});
@archyntabona
Copy link

Hey Thomas,
I have a quick question, as I am updating my application to now use Angular Ivy. I am running in to an issue where
all my test are failing because of an import incompatibility for the "readFirst" (operator) import { readFirst } from '@nrwl/nx/testing';
I changed it to import { readFirst } from '@nrwl/angular/testing'; that breaks my testing with a conflicting between 'rxjs/internal/Observable'.

Thank you for your time, I hope my explanation was clear enough.

@spirosdi
Copy link

spirosdi commented Sep 10, 2020

Hey Thomas,
I have a quick question, as I am updating my application to now use Angular Ivy. I am running in to an issue where
all my test are failing because of an import incompatibility for the "readFirst" (operator) import { readFirst } from '@nrwl/nx/testing';
I changed it to import { readFirst } from '@nrwl/angular/testing'; that breaks my testing with a conflicting between 'rxjs/internal/Observable'.

Thank you for your time, I hope my explanation was clear enough.

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>'.

@spirosdi
Copy link

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