'TypeError: Cannot read properties of undefined - React Redux Toolkit Testing

In my React project I'm using redux toolkit and all is working fine on the UI side, but I'm running into the following error now I've come to testing my code (Note - I have chosen RTL as my library of choice, and I know I should test as I write code but I'm here now anyway).

TypeError: Cannot read property 'isSearching' of undefined

      16 |   const postState = useSelector(selectInitialPostsState);
    > 17 |   const isSearching = postState.isSearching;

As can be seen above, I import my initialState and save it to postState in my component, and then access isSearching from there - which works absolutely fine in my code and my App. I have accessed my state properties this way throughout my code as it seemed easier than having to write an individual selector for each state property used.

In my test file, I have resorted to Redux's test writing docs and have tried two different ways of rendering my store via manipulating RTL's render() method. The first function written locally in my test file, which for the time being I have commented out, and the second is imported into my test file from test-utils. Both methods give me the same errors.

Here is my test file:

import React from 'react';
// import { Provider } from 'react-redux';
// import { render as rtlRender, screen, fireEvent, cleanup } from '@testing-library/react';  
// import { store } from '../../app/store';

import { render, fireEvent, screen } from '../../../utilities/test-utils';
import Header from '../Header';

// const render = component => rtlRender(
//   <Provider store={store}>   
//     {component}
//   </Provider>
// );

describe('Header Component', () => {  
  // let component;
  // beforeEach(() => {
  //   component = render(<Header />)
  // });

  it('renders without crashing', () => {
    render(<Header />);
    // expect(screen.getByText('Reddit')).toBeInTheDocument();
  });
});

And here is my test-utils file:

import React from 'react'; 
import { render as rtlRender } from '@testing-library/react';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom'; 
// Import your own reducer
import sideNavReducer from '../src/features/sideNavSlice';
import postReducer from '../src/features/postSlice'; 

function render(
  ui,
  {
    preloadedState,
    store = configureStore({ reducer: { sideNav: sideNavReducer, posts: postReducer }, preloadedState }),
    ...renderOptions
  } = {}
) {
  function Wrapper({ children }) {
    return ( 
      <Provider store={store}>
        <BrowserRouter>{children}</BrowserRouter>
      </Provider>
    )
  }
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}

// re-export everything
export * from '@testing-library/react'
//override render method
export { render }

I have been able to prevent this error by creating a selector that saves the property directly, and accessing it in my component as isSearching without having to use postState.isSearching, but then the next error comes up about the next property I have to do this with, and then the next and so on.

It seems that in my test file, postState is undefined, whereas in my console, it holds the initialState from my slice file. Can anyone please advise why this is? I don't see a reason why I would have to write numerous selectors that directly access properties, rather than accessing them through an initialState selector. I can't seem to grasp how it works in my functioning code but not in my test code.

For further context if required, here is the mentioned initialState and selector from my slice file:

const postSlice = createSlice({
  name: 'posts', 
  initialState: {
    allPosts: [], 
    popularPosts: [],
    sportPosts: [],
    newsPosts: [],
    savedPosts: [],
    hiddenPosts: [],
    reportedPosts: [],
    dataLoading: true, 
    allPostsShown: true,
    ellipsisClicked: false, 
    reportModal: false,
    modalClosed: true,
    imgClicked: false,
    searchedPostsFound: false,
    searchedPosts: [],
    isSearching: false,
    searchText: "",
  },
});

export const selectInitialPostsState = state => state.posts; 

And here is how it is used in my component. I have only included the relevant code.

const SideNav = () => {
 const postState = useSelector(selectInitialPostsState);
 const isSearching = postState.isSearching; 

 useEffect(() => {
    !isSearching && setSearchText(""); 
    if(!searchText){
      dispatch(userNoSearch());
      dispatch(searchedPostsFound({ ids: undefined, text: searchText }));   // reset searchedPosts state array 
    }
  }, [dispatch, searchText, isSearching]);

  useEffect(() => {
    if(isSearching){
      dispatch(searchedPostsFound());
    }
  }, [dispatch, isSearching, search]);
}


Solution 1:[1]

If you want to load the initialState of your component, you need to send it as a parameter to rtlRender with your component.

it('renders without crashing', () => {
  rtlRender(<Header />, { preloadedState: mockStateHere });
  // expect(screen.getByText('Reddit')).toBeInTheDocument();
});

your mockStateHere structure needs to resemble what your redux store's state looks like as best as possible, using entities and id's etc

If you look at how you are building the render function in this example, you'll see the param preloadedState being deconstructed along with store which has the default configuration in this example:

function render(
  ui,
  {
    preloadedState,
    store = configureStore({ reducer: { sideNav: sideNavReducer, posts: postReducer }, preloadedState }),
    ...renderOptions
  } = {}
...
) 

that's the same preloadedState value that you send in with your ui component, which is being loaded into your store with the configureStore method and that store value will be sent into the Provider

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 DevAchievem