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

At what point does Math.random() begin to cycle through its values?

After running this simple test in nodejs overnight, I found that Math.random() did not repeat. While I understand that the values will eventually repeat at some point, is there a predictable timeframe for when it's likely to happen? let v = {}; for ( ...

How can I display a calendar with a complete month view using ng-repeat?

I was trying to replicate a table similar to the one shown in this image: (disregard the styling). I am struggling with how to properly format the data to create a similar table in HTML. $scope.toddlers = [ { "name": "a", "day": 1, "total": 3 }, { ...

Securely store files by encrypting them with Node.js before saving to the disk

At the moment, I am making use of the multer library to store files on the File system. This particular application is built using Node and Express. I currently have a process in place where I save the file on the server first and then encrypt it. After e ...

Managing additional components in request JSON when communicating with DialogFlow, previously known as Api.ai

My current challenge involves enhancing the information sent in a JSON request from my application to DialogFlow. While I am familiar with triggering events to send data calling an intent through the method described in Sending Parameters in a Query Reques ...

Ways to insert a line break within a jQuery function using text()

I am looking to add a new line in my jQuery function. $.ajax({ url:"http: //192.168.1.4/Experiements/webservices/api.php", type:"GET", dataType:"json", data:{type:"login", UserName:userId,Password:userPassword}, ...

Prevent assigning values to rxjs observables recursively

I am seeking suggestions on how to enhance the code provided below. I will outline the issue and present my current solution, which I aim to refine. The code is written in Angular 4 (TS). herelistOfItems$: Observable<Array<Item>>; // Fetchin ...

Guide to invoking the API prior to shutting down the browser window in Angular4

Currently, I am working on an Angular 4 application that consists of 5 components. My goal is to trigger an API call when the user closes the browser window from any one of these components. However, I have encountered an issue where the API does not get ...

Which tools should I combine with Angular for developing both Android and iOS applications?

My primary focus has been on developing web apps using Angular, but now I am interested in creating native Android and iOS apps with Angular. I have heard about using Cordova, Capacitor, and NativeScript for this purpose, as alternatives to Ionic due to pe ...

Reusing observables after encountering errors

Is there a way to handle an Observable that errors out and stops emitting values in Angular templates? For instance, if I have a Subject that switches to an HTTP call using switchMap and the call fails due to incorrect user input. How do I ensure that the ...

Error: No injection provider found for function(){}!

After countless hours of setting up a custom AOT in Angular 7 project without CLI and debugging, I have encountered the following error: Uncaught Error: StaticInjectorError(Platform: core)[function(){}]: NullInjectorError: No provider for function(){}! ...

Steps to correctly reset an XMLHttpRequest object

I'm currently working on a project where I send AJAX requests, receive responses, and replace text based on those responses. However, it seems like I may be missing a fundamental concept in my approach. request.onreadystatechange = () => { if (r ...

Issue with login form in IONIC: Form only functions after page is refreshed

Encountering an issue with my Ionic login form where the submit button gets disabled due to invalid form even when it's not, or sometimes displays a console error stating form is invalid along with null inputs. This problem seems to have surfaced afte ...

Necessary within a JavaScript Class

As a newcomer to using classes in JavaScript, I've been exploring the best practices and wondering about how 'requires' work when used within a class. For example, let's say I want to craft an IoT Connection class for connecting to the ...

Experiencing an issue with excessive re-renders in React as it restricts the number of renders to avoid getting stuck in an infinite loop while attempting to

I am working with a React component import React, {useState} from 'react'; function App() { const [number, setNumber] = useState(12); return ( <> <h1>The number value is: {number}</h1> <div className=" ...

I'm curious if it's possible to utilize Raspberry Pi GPIO pins within a JavaScript frontend

Is it possible to utilize Raspberry Pi's GPIO pins in Javascript? Specifically, I am interested in reading the values of the Raspberry Pi PIR sensor without having separate Python and Javascript applications. Ideally, I would like a solution that inte ...

Update the icon within the expandable display

Is there a way to change the plus sign to a minus sign in an expandable table view on click? (changing fa fa-plus to fa fa-minus) The goal is to have the plus sign displayed when the view is compressed and the minus sign displayed when it is expanded. If ...

What is the best way to access and utilize an id within an angular component's routing system?

I am currently working on an Angular application, and this is my first experience with JS. I have a main view where I display several elements, such as movies, each of which is clickable and links to a detailed view of the movie. My question is how can I h ...

Trouble with sending data to child components via props

I've been trying to pass a prop to a child component in React, but when I check the props using console log, it doesn't show up at all. Even the key things is missing from the props. Any assistance with this would be greatly appreciated. export ...

Node.js bypasses unit test validation

As a beginner in BDD with Node.js, I have a controller function defined as follows: var getUser = function(username, done) { console.log('prints'); User.findOne({ 'local.username': username }, function (err, user) { ...

Is it possible to utilize $regex alongside $all in mongoDB?

In my current project, I am facing a challenge where I need to handle an array of strings received from the frontend. Each document in my mongoDB database also has its own array of keywords. The tricky part is that the strings sent from the frontend migh ...