Exploring unit tests: Customizing an NGRX selector generated by entityAdapter.getSelectors()

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!

Answer №1

It is possible to convert booksSelector into any data type:

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

Answer №2

To properly select features, you should use a feature selector as an input to the getSelectors method:

const selectBookFeatureState =
  createFeatureSelector<BooksFeature>('books');

const { selectAll, selectTotal } = booksAdapter.getSelectors(selectBookFeatureState)

If this approach doesn't yield results, consider creating selectors in the following manner:

const selectAllBooks = createSelector(
  selectBookFeatureState, 
  selectAll
)

const selectTotalBooks = createSelector(
  selectBookFeatureState, 
  selectTotal
)

Then utilize selectAllBooks instead of selectAll, and selectTotalBooks instead of selectTotal

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Edit the settings for the dual-axis line chart's parameters

After countless hours of scouring the Internet and numerous attempts, I have come to the decision to seek help by posting my issue on this forum. I must confess, I am not the best developer. My approach usually involves finding pre-existing code that I ca ...

What methods are available to adjust the header color on various pages?

I have a solution that works for one location, but I need to add red color to multiple locations. How can I achieve this? import { useRouter } from "next/router"; function Header() { const router = useRouter(); return ( <> & ...

What is the best way to replicate a synchronous ajax call? (mimicking synchronous behavior with asynchronous methods)

Given that a "native" synchronous ajax call can block the user interface of the browser, it may not be suitable for many real-world scenarios (including mine). I am curious to know if there is a way to mimic a synchronous (blocking) ajax call using an asy ...

What causes an ObjectUnsubscribedError to be triggered when removing and then re-adding a child component in Angular?

Within a parent component, there is a list: items: SomeType; The values of this list are obtained from a service: this.someService.items$.subscribe(items => { this.items = items; }); At some point, the list is updated with new criteria: this.some ...

Eliminate the registration message from TinyMCE by importing a package

Looking to create a TinyMCE React package, I've been using import { Editor } from '@tinymce/tinymce-react'; However, I'm encountering this message - To remove the message, typically you would add the API key to the .js. in your <scr ...

Objects vanish 10 seconds after appearing [Angular2, *ngFor]

My Angular2 template is quite straightforward: <span *ngFor="let item of items"> {{ item.description }} </span> Here is the TypeScript logic for it: let list = new Map(); for(let j = 0; j < 100; j++) { list.set(j, { description: j.toS ...

The fancybox's excess content is concealed

I've recently integrated fancybox 2 into my project and have encountered an issue when appending HTML content to the modal. I disabled scrolling, but now any overflowing content is being hidden. Could this be related to the max-height of the modal? Ho ...

Using asynchronous data in Angular 2 animations

Currently, I have developed a component that fetches a dataset of skills from my database. Each skill in the dataset contains a title and a percentage value. My objective is to set the initial width value of each div to 0% and then dynamically adjust it t ...

What is the best way to showcase a chart using jquery?

Is there a way to incorporate trendlines or target lines in highcharts similar to fusion chart? I have been able to draw them successfully in fusion charts. Check out this fiddle link: http://jsfiddle.net/Tu57h/139/ I attempted to recreate the same in hi ...

Automatically Assigning a Default Value to a Column Using SEQUELIZE ORM

When fetching data from a database using Sequelize ORM, I need to set a default value. Here is an example of the SQL query: SELECT a.affiliate_id, a.active AS current_state, IF(MAX(cn.contract_id) IS NULL ,0, IF(DATEDIFF(NOW(),MAX(cn.contract_date) ...

Finding the index of a column based on a continuous sequence of values can be achieved by following these

Consider this sequence of numbers representing components on a page: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Every set of 3 numbers makes up a page, with indexing restarting for each new page. Essentially, it follo ...

Delay Export of React Component Until After Request in Shopify App Development

Being a newbie in Shopify App Development, React, and Next.js, I may have a silly question. Currently, I am making a request to a website and using the response in the React component that I want to export/render. To avoid it being undefined, I need to wai ...

Is there a way to remove specific items from my array in Vue.js?

Essentially, I am using an HTML select element that is populated with arrays of registered users. <label > <b> <i style="opacity:0.8">Users:</i> </b> </label>&nbsp;&nbsp; <select class=&quo ...

Switch up your text display using JQuery and toggle between different texts

I have implemented a jQuery script to toggle the display of certain paragraphs when the "More" link is clicked. However, I am facing an issue where the link always displays "More" even after the content has been expanded. I want it to change to "Less" once ...

Disappearing Ionic React useState value issue encountered when passing it as a prop parameter in a function

After transitioning from JavaScript to TypeScript, I encountered an issue with my useState hook not printing anything when used in a parent component. My confusion also extends to importing types in TypeScript. interface Props { sendTextMessage: (text? ...

How to display document files (.doc or .docx) using a byte array in Angular 4

I am facing a challenge in viewing all the attachments submitted by users. While I can easily view PDF and image files, I seem to have trouble with files having .doc or .docx extensions. Here is my current approach: let file = null; if (extension === &a ...

Attempting to duplicate Codepen's code onto my local machine

Trying to figure out how to make this work locally after finding it on codepen https://codepen.io/oxla/pen/awmMYY Seems like everything works except for the functionality part. I've included the JS File and the latest Jquery in my code. <head&g ...

Issue when attempting to animate an SVG point using translateX transformation

I am attempting to create a basic animation using the translate X property on a section of my svg when hovering over the element. Here is the code I have so far: <html> <style> .big-dot:hover { transform: translateX(20px); animat ...

Animating images with Jquery

As a beginner in Javascript and Jquery, I am currently learning about image animation. However, I have encountered a question regarding moving an image from bottom left to top right within the window. Is there a more efficient way to achieve this compared ...

Implementing a boolean value in PrimeNG's p-dropdown version 7.x

I need a p-dropdown with two options to be selected based on the boolean value of the control. The control (NOTIF_ALL) is assigned a boolean value oldPendingTasksOptions=[ {"oldPendingTaskId": false, "oldPendingTasksName": "Not a ...