'In unit test: How to override an NGRX selector which is created by entityAdapter.getSelectors()
Assume that our app has a books page. We are using: Angular, NGRX, jest.
Some lines of code to give a context (see actual problem below):
Interfaces of the books page's state:
export interface Book {
bookID: string;
whatever: string;
}
export interface Books extends EntityState<Book> {
error: boolean;
loaded: boolean;
}
export interface BooksFeature extends RootState {
books: Books;
//...
}
Added to ngrx store as feature:
@NgModule({
imports: [
StoreModule.forFeature('books', booksReducer),
//...
]
ngrx entityAdapter is created:
export const booksAdapter = createEntityAdapter<Book>({
selectId: (book: Book): string => book.bookID,
});
Create booksSelector from booksAdapter's selectAll
const { selectAll, selectTotal } = booksAdapter.getSelectors((state: BooksFeature) => state.books);
export const booksSelector = selectAll;
Assign booksSelector to the component property: books$
public books$ = this.store.pipe(select(booksSelector));
Then the books$ observable is used for many things (eg. <div *ngIf="(books$ | async).length">, etc...).
The goal: Assume that I would like to unit test separately if the books$ observable has always the same value as what the booksSelector broadcasts.
Normally I would do the following in the component's books.component.spec.ts file:
General setup for component test:
//...
describe('BooksComponent', () => {
let spectator: Spectator<BooksComponent>
let store: MockStore;
const initialState: RootState = {
//...
books: {
ids: [],
entities: {},
error: false,
loaded: false
}
};
const testBook: Book = {
bookID: 'bookid_1',
whatever: 'whatever'
};
const createComponent = createComponentFactory({
component: BooksComponent,
providers: [
provideMockStore({ initialState })
],
imports: [
StoreModule.forRoot({}),
detectChanges: false
]
});
beforeEach(() => {
spectator = createComponent();
store = spectator.inject(MockStore);
});
afterEach(() => {
jest.clearAllMocks();
});
//...
And the important part:
//...
describe('books$', () => {
it('books$ should have the same value as what booksSelector gives', () => {
const testBooks: Book[] = [testBook];
const expected = cold('a', { a: testBooks });
const mockBooksSelector = store.overrideSelector(booksSelector, testBooks);
expect(spectator.component.books$).toBeObservable(expected);
});
//... Then I would like to use the mockBooksSelector.setResult(...) method too for other test cases
});
//...
The problem with this is that the MockStore's overrideSelector method expects a Selector as first parameter, but the entityAdapter's getSelectors method returns with a selectAll method that has a different type.
Please let me know how could I replace this test with a proper solution!
Please keep in mind, that the problem is simplified to keep it focused and I'm not looking for solutions like these:
- Test instead if the store.pipe is called with the proper select.
- Change the state manually in order to have the wished value given by
booksSelector. - Solutions that change things not only in the .spec file. (I mean, if it's really inevitable then OK)
Thx!
Solution 1:[1]
You need to use feature selector as input to getSelectors:
const selectBookFeatureState =
createFeatureSelector<BooksFeature>('books');
const { selectAll, selectTotal } = booksAdapter.getSelectors(selectBookFeatureState)
and if that doesn't work you can try creating selectors like this:
const selectAllBooks = createSelector(
selectBookFeatureState,
selectAll
)
const selectTotalBooks = createSelector(
selectBookFeatureState,
selectTotal
)
and use selectAllBooks instead selectAll and selectTotalBooks instead selectTotal
Solution 2:[2]
You can cast booksSelector to any type:
const mockBooksSelector = store.overrideSelector(booksSelector as any, testBooks)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Xesenix |
| Solution 2 | Zach Jensz |
