Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Testing waiting for useDispatch and state change

I'm testing a component that I need to wait for a dispatch call to be made and then wait for the 'loading' state to change as well.

I'm using jest.mock to deliver the results of the dispatch however it appears as though the results aren't being delivered as far as I can tell...

My Post Detail Component: useEffect(() => {

    const postDetail = async () => {
        
        await dispatch(postDetailView(postID))
        console.log('post Detail', postID)
        setLoading(false)
    }

    postDetail()

}, [dispatch])

My Test Setup:

const mockDispatch = jest.fn();

jest.mock('react-redux', () => ({
    ...jest.requireActual('react-redux'),
    useDispatch: () => mockDispatch.mockReturnValueOnce({
        _id: '12345',
        title:'A New Post',
        description: 'A New Description'
    }) // Return a value since I'm expecting a value to be returned before I redirect
}))



describe('Post Detail', () => {

    it('Post Detail Get View', async () => {

        const result = await waitFor(() =>  render(
        <MemoryRouter initialEntries={['/detail/12345']}>
            <Provider store={store}>
                <Routes>
                    <Route path='/detail/:postID' exact element={<PostDetail/>} />
                </Routes>
            </Provider>
        </MemoryRouter>))
    
        // This produces and Error that title does not exist
        const postElementTitle = screen.getByText(/title/i)

        //This test passes
        expect(mockDispatch).toHaveBeenCalledTimes(1)
        
    })

})

What I'm hoping would happen is the component would load in the waitFor block and call dispatch, get the results, but that does not seem to be happening, and oddly the dispatch call does get called successfully but the results don't seem to be making it to the component.

The Error results:

 TestingLibraryElementError: Unable to find an element with the text: /title/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    Ignored nodes: comments, <script />, <style />
    <body>
      <div>
        <div
          class="card"
        >
          <div
            class="row"
          >
            <div
              class="col"
            >
              <a
                href="/detail/undefined" // this is where the ID of the dispatch results should appear but it's coming up blank
              >
                <p /> //This is where title from the dispatch tests should be appearing but it's blank
              </a>
            </div>
            <div
              class="col"
            >
              <p />
            </div>
            <div
              class="col"
            >
              <button
                class="btn btn-outline-primary"
                type="button"
              >
                Update
              </button>
              <button
                class="btn btn-outline-danger"
                type="button"
              >
                Delete
              </button>
            </div>
            
          </div>
        </div>
      </div>
    </body>

Versions I'm using:

"@reduxjs/toolkit": "^1.7.1",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"react": "^17.0.2",
"react-redux": "^7.2.6",
"react-router-dom": "^6.2.1",
"react-scripts": "5.0.0",

To recap what's going on (I think) is that the dispatch element is getting called but the results are not getting read by the component itself. I'm not sure how to remedy this or if anyone can see any glaring missteps on my part. Thanks in advance.

like image 937
Linus Avatar asked Jul 10 '26 12:07

Linus


1 Answers

I figured out what I did wrong and that I also had an incorrect approach to testing a component itself. Also I learned a lot about redux and how complicated it is to test but the learning was a good thing for me to figure out more ins and outs of redux.

TestingFile

const store = configureStore({
    reducer:{
        post: postReducer,
        alert: alertReducer
    }
})

const MockComponent = (props={}, history={}) => {
    return(
        <BrowserRouter>
            <Provider store={store}>
                <PostDetail/>
            </Provider>
        </BrowserRouter>)
}

const mockDispatch = jest.fn();
// const mockSelector = jest.fn()

jest.mock('react-redux', () => ({
    ...jest.requireActual('react-redux'),
    useDispatch: () => mockDispatch
    // useSelector: () => mockSelector.mockReturnValueOnce(true)
}))

// // mock useNavigate
describe('Post Detail', ()=>{

    it('post detail', async () => {

        
        render(MockComponent())  
        
        // Use dispatch to update the store correctly
        store.dispatch(postSliceActions.postDetail({_id:1, title:'A new Post', description: 'A new Desc'}))
        
        // Check that dispatch was called within the component
        expect(mockDispatch).toHaveBeenCalledTimes(1)

        // Ensure the loading screen has time to go away
        await waitForElementToBeRemoved(() => screen.queryByText(/Loading/i))

        // Check for the post title in the component
        expect(screen.getByText(/a new post/i)).toBeInTheDocument()
        

        // screen.debug()

    })

})

Also this article helped me as well, especially with the waiting for the element to be removed...https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning

Thanks,

like image 132
Linus Avatar answered Jul 14 '26 12:07

Linus



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!