question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Async Thunk error running tests

See original GitHub issue

Hello everybody, I’m writing some tests for my react app, however i’m receiving an error:

TypeError: Cannot read properties of undefined (reading 'pending')

      15 |   extraReducers: (builder) => {
      16 |     builder
    > 17 |       .addCase(fetchCategories.pending, (state, { meta }) => {
         |                                ^
      18 |         const { requestId } = meta
      19 |
      20 |         if (state.loading === 'idle') {

If i have two or more middlewares running, i don’t have this problem, but if i have less, i get this error. I can’t find a solution for this, hope anyone can save me, in development my code runs perfectly.

This is my code:

// store.ts
import { combineReducers, configureStore } from '@reduxjs/toolkit'

import autoLoginMiddleware from 'features/middlewares/auto-login.middleware'
import {
  persistReducer,
  persistStore,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import logger from 'redux-logger'

import userReducer from 'features/user/user.slice'
import cartReducer from 'features/cart/cart.slice'
import categoriesReducer from 'features/categories/category.slice'

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['cart'],
}

const persistedReducer = persistReducer(
  persistConfig,
  combineReducers({
    user: userReducer,
    cart: cartReducer,
    categories: categoriesReducer,
  })
)

export const store = configureStore({
  reducer: persistedReducer,
  devTools: process.env.NODE_ENV !== 'production',
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat(logger, autoLoginMiddleware),
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const persistor = persistStore(store)
export default store
// cart-dropdown.test.tsx
import '@testing-library/jest-dom'
import mockCartItems from 'mocks/cart-items'
import { render, screen, fireEvent, act } from 'utils/test'

import CartDropdown from './cart-dropdown.component'
import store from 'app/store'
import { setCartItems } from 'features/cart/cart.slice'

const mockedUseNavigate = jest.fn()

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: () => mockedUseNavigate,
}))

describe('[Component] CartDropdown', () => {
  beforeEach(() => render(<CartDropdown />))

  it('Starts with cart empty', () => {
    expect(screen.getByText(/Your cart is empty/i)).toBeInTheDocument()
  })

  it('Showing items when added', () => {
    act(() => {
      store.dispatch(setCartItems(mockCartItems))
    })

    expect(screen.getAllByRole('img').length).toBe(3)
  })

  it('Button redirecting to checkout', () => {
    const button = screen.getByRole('button')
    fireEvent.click(button)

    expect(mockedUseNavigate).toHaveBeenCalled()
  })
})
// category.thunk.ts
import { createAsyncThunk } from '@reduxjs/toolkit'

import { apolloClient } from 'app/api'

import { GET_ALL_CATEGORIES } from 'apollo/categories.queries'
import { GET_ALL_PRODUCTS } from 'apollo/products.queries'

import type { GqlCategory, Category, GqlProduct } from './category.types'
import { getCategoryProducts } from 'helpers/products'
import { RootState } from 'app/store'

export const fetchCategories = createAsyncThunk<
  Category[],
  undefined,
  { state: RootState }
>('categories/fetchCategories', async (_, { getState, requestId }) => {
  const { currentRequestId, loading } = getState().categories

  if (loading !== 'pending' || requestId !== currentRequestId) return

  const {
    data: { Categories },
  } = await apolloClient.query({ query: GET_ALL_CATEGORIES })

  if (Categories) {
    let {
      data: { Products: dbProducts },
    } = await apolloClient.query({ query: GET_ALL_PRODUCTS })

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    dbProducts = dbProducts.map(({ __typename, ...product }: GqlProduct) => {
      return { ...product }
    })

    const categoriesMap = Categories.map(
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      ({ __typename, active, ...category }: GqlCategory) => {
        if (active === true)
          return {
            ...category,
            items: getCategoryProducts(category, dbProducts),
          }
      }
    )

    return categoriesMap
  }
})
// category.slice.ts
import { createSlice } from '@reduxjs/toolkit'

import { fetchCategories } from './category.thunk'
import type { CategorySliceState } from './category.types'

const categorySlice = createSlice({
  name: 'category',
  initialState: {
    categories: [],
    loading: 'idle',
    error: null,
    currentRequestId: undefined,
  } as CategorySliceState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchCategories.pending, (state, { meta }) => {
        const { requestId } = meta

        if (state.loading === 'idle') {
          state.loading = 'pending'
          state.error = null
          state.currentRequestId = requestId
        }
      })
      .addCase(fetchCategories.fulfilled, (state, action) => {
        const { requestId } = action.meta

        if (
          state.loading === 'pending' &&
          state.currentRequestId === requestId
        ) {
          state.loading = 'idle'
          state.error = null
          state.categories = action.payload
          state.currentRequestId = undefined
        }
      })
      .addCase(fetchCategories.rejected, (state, action) => {
        const { requestId } = action.meta

        if (
          state.loading === 'pending' &&
          state.currentRequestId === requestId
        ) {
          state.loading = 'idle'
          state.categories = []
          state.currentRequestId = undefined
          state.error = action.error
        }
      })
  },
})

export default categorySlice.reducer

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:1
  • Comments:7 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
markeriksoncommented, Jun 6, 2022

Ah. @jzorzetti the issue here really has nothing to do with Apollo at all. The problem is the import { store } from './store'.

This is specifically why we tell users you should not directly import the store into other app files - it’s likely to cause circular import issues!

If you absolutely must have access to the store in this file, use the “setter/injection” approach shown in our FAQ here:

https://redux.js.org/faq/code-structure#how-can-i-use-the-redux-store-in-non-component-files

0reactions
jhones-devcommented, Jun 6, 2022

circulardeps

After removing the type import from app.ts, the only circular dep remaining is from the apollo client, however, i need apollo client to query my stuff (thats why async thunks are needed i think). Can’t I use apollo with Redux?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Async thunk doesn't get executed when running trough test files
I am currently working on a migration, removing react context and using reduxt toolkit. Basically created the basics for it:.
Read more >
How to Write Unit Tests for Asynchronous Redux Thunks in ...
Step 1: Analyze dependencies · Step 2: Define expected behaviours · Step 3: Mock dependencies · Step 4: Implement the tests · Step...
Read more >
createAsyncThunk - Redux Toolkit
createAsyncThunk. Overview​. A function that accepts a Redux action type string and a callback function that should return a promise.
Read more >
Async waits in React Testing Library - Reflect.run
Handling when the API call returns with an error or timeout via Promise chaining or a try/catch block and async/await.
Read more >
Testing and error handling patterns in Next.js - LogRocket Blog
Check out these different ways to run tests and handle errors in Next.js ... export const getStaticProps = async () => { try...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found