Burnee
Burnee

Reputation: 2623

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:

Thx!

Upvotes: 1

Views: 2048

Answers (2)

user19102932
user19102932

Reputation: 36

You can cast booksSelector to any type:

const mockBooksSelector = store.overrideSelector(booksSelector as any, testBooks)

Upvotes: 2

Xesenix
Xesenix

Reputation: 2548

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

Upvotes: 0

Related Questions