Let's imagine a scenario where our application includes a books page. We are utilizing the following technologies: Angular, NGRX, jest.
To provide some context, here are a few lines of code:
The interfaces for the state of the books page:
export interface Book {
bookID: string;
whatever: string;
}
export interface Books extends EntityState<Book> {
error: boolean;
loaded: boolean;
}
export interface BooksFeature extends RootState {
books: Books;
//...
}
The books feature is added to the ngrx store:
@NgModule({
imports: [
StoreModule.forFeature('books', booksReducer),
//...
]
An entityAdapter for ngrx is created:
export const booksAdapter = createEntityAdapter<Book>({
selectId: (book: Book): string => book.bookID,
});
We create a booksSelector
using the selectAll
method from the booksAdapter
.
const { selectAll, selectTotal } = booksAdapter.getSelectors((state: BooksFeature) => state.books);
export const booksSelector = selectAll;
We assign the booksSelector
to the component property: books$
public books$ = this.store.pipe(select(booksSelector));
Subsequently, we use the books$ observable for various purposes (e.g.
<div *ngIf="(books$ | async).length">
, etc...).
The objective: Suppose that I want to separately unit test whether the books$
observable always reflects the same value as what the booksSelector
provides.
In a typical scenario, I would perform the following steps in the component's books.component.spec.ts
file:
Setting up the general structure for the 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 crucial 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 issue with this approach is that the overrideSelector
method of MockStore
expects a Selector
as the first parameter, whereas the getSelectors
method of entityAdapter
returns a selectAll
method with a different type.
Please advise on how I can replace this test with an appropriate solution!
Kindly note that the problem has been simplified to maintain focus, and I'm not seeking solutions such as:
- Testing if the store.pipe is invoked with the correct select.
- Manually altering the state to match the desired value provided by
booksSelector
. - Solutions that involve changes beyond the .spec file. (However, if absolutely necessary, then it's acceptable)
Thank you!