Unable to run test with redux-toolkit RTK-Query: TypeError: Cannot read property 'injectEndpoints' of undefined
Asked Answered
C

1

7

trying to run the first test with redux-toolkit rtk query and got error that api is undefined. What I did wrong?

  ● Test suite failed to run

    TypeError: Cannot read property 'injectEndpoints' of undefined

      33 | };
      34 |
    > 35 | export const authApi = api.injectEndpoints({
         |                            ^
      36 |     endpoints: (build) => ({
      37 |         login: build.mutation<ILoginResponse, ILoginData>({
      38 |             query: ({ username, password, captchaToken }) => {

      at Object.<anonymous> (src/api/auth.ts:35:28)
      at Object.<anonymous> (src/features/auth/authSlice.ts:2:1)
      at Object.<anonymous> (src/api/index.ts:10:1)
      at Object.<anonymous> (src/mocks/handler.ts:3:1)
      at Object.<anonymous> (src/mocks/server.ts:3:1)
      at Object.<anonymous> (src/setupTests.ts:6:1)
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
      at runJest (node_modules/@jest/core/build/runJest.js:404:19)
      at _run10000 (node_modules/@jest/core/build/cli/index.js:320:7)
      at runCLI (node_modules/@jest/core/build/cli/index.js:173:3)

This is my test:

it('should reset roleType on role change', () => {
    renderWithProviders(
        <Form
            initialValues={{
                role: 6,
                roleType: 7,
                companyId: 2,
            }}
            mutators={formMutators}
            onSubmit={jest.fn()}
        >
            {({ form }) => (
                <>
                    <RoleFields setValue={form.mutators.setValue} />
                </>
            )}
        </Form>
    );
});

There is nothing here yet just trying to render without errors.

And this is renderWithProviders function:

interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
    preloadedState?: PreloadedState<RootState>;
    store?: AppStore;
}

export function renderWithProviders(
    ui: React.ReactElement,
    {
        preloadedState = {},
        // Automatically create a store instance if no store was passed in
        store = setupStore(preloadedState),
        ...renderOptions
    }: ExtendedRenderOptions = {}
) {
    function Wrapper({ children }: PropsWithChildren<Record<string, unknown>>): JSX.Element {
        return <Provider store={store}>{children}</Provider>;
    }

    // Return an object with the store and all of RTL's query functions
    return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}

setupTests:

import '@testing-library/jest-dom/extend-expect';
import { server } from './mocks/server';
import { api } from './api';
import { setupStore } from './app/store';

const store = setupStore();

// Establish API mocking before all tests.
beforeAll(() => {
    server.listen();
});

// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => {
    server.resetHandlers();
    // This is the solution to clear RTK Query cache after each test
    store.dispatch(api.util.resetApiState());
});

// Clean up after the tests are finished.
afterAll(() => server.close());

Store setup:

import { configureStore, ThunkAction, Action, combineReducers } from '@reduxjs/toolkit';
import type { PreloadedState } from '@reduxjs/toolkit';

import { api } from '../api';
import { authSlice } from '../features/auth/authSlice';

// Create the root reducer separately so we can extract the RootState type
const rootReducer = combineReducers({
    [api.reducerPath]: api.reducer,
    [authSlice.name]: authSlice.reducer,
});

export const setupStore = (preloadedState?: PreloadedState<RootState>) => {
    return configureStore({
        reducer: rootReducer,
        middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware),
        preloadedState,
    });
};

Please help what here is wrong, have no idea....

UPD: Looks like the problem here:

import { logout } from '../features/auth/authSlice';

....

const baseQueryWithAuthCheck: BaseQueryFn<
    string | FetchArgs,
    unknown,
    FetchBaseQueryError
> = async (args, api, extraOptions) => {
    const result = await baseQuery(args, api, extraOptions);

    if (result.error && result.error.status === 401) {
        api.dispatch(logout());
    }
    return result;
};

export const api = createApi({
    reducerPath: 'api',
    baseQuery: baseQueryWithAuthCheck,
    tagTypes: ALL_TAGS_TYPES,
    endpoints: () => ({}),
});

I'm doing import logout action from authSlice, and that authSlice do import from api to build matchers for extraReducers

import { authApi } from '../../api/auth';

....

export const authSlice = createSlice({
    name: 'auth',
    initialState,
    reducers: {
        logout: (state) => {
            state.token = null;
            authStorage.clear();
        },
    },
    extraReducers: (builder) => {
        builder
            .addMatcher(authApi.endpoints.login.matchPending, (state) => {
                state.loading = 'pending';
            })
....
Clementineclementis answered 17/10, 2022 at 13:14 Comment(1)
Where you able to find a solution?Jacobus
C
4

I can't tell for sure, I no longer have access to this project, but as far as I remember issue was caused by calling api on baseQuery function, don't know why but if I remove this call everything ok

    const baseQueryWithAuthCheck: BaseQueryFn<
    string | FetchArgs,
    unknown,
    FetchBaseQueryError
> = async (args, api, extraOptions) => {
    const result = await baseQuery(args, api, extraOptions);

    if (result.error && result.error.status === 401) {
// the issue was caused because of this call
        api.dispatch(logout());
    }
    return result;
};

export const api = createApi({
    reducerPath: 'api',
    baseQuery: baseQueryWithAuthCheck,
    tagTypes: ALL_TAGS_TYPES,
    endpoints: () => ({}),
});

Clementineclementis answered 14/7, 2023 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.