#reactjs #redux #react-testing-library
#reactjs #redux #react-testing-library
Вопрос:
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import { render, screen, fireEvent } from '@testing-library/react';
import MyApp from './MyApp ';
const initialState = {};
const mockStore = configureStore(initialState);
describe('<MyApp />', () => {
it('click button and shows modal', () => {
render(
<Provider store={mockStore}>
<MyApp />
</Provider>
);
fireEvent.click(screen.getByText('ADD MIOU'));
expect(queryByText('Add MIOU Setting')).toBeInTheDocument();
});
});
Я использую jest и redux toolkit reactjs
и пытаюсь создать макет хранилища для написания теста.
Но получил следующую ошибку
Ошибка типа: store.getState не является функцией
Есть ли какой-нибудь способ это исправить? Я что-то упустил?
Ответ №1:
Я предполагаю, что вы пытаетесь протестировать подключенный компонент и ожидаете, что (1) создатели и редукторы действий будут запущены и (2) состояние redux будет обновлено как часть вашего теста?
Я не использовал redux-mock-store, но я вижу следующее примечание в их документации, что наводит меня на мысль, что эта библиотека может работать не так, как вы ожидаете:
Пожалуйста, обратите внимание, что эта библиотека предназначена для тестирования логики, связанной с действием, а не с редуктором. Другими словами, он не обновляет хранилище Redux.
Я предлагаю вам попробовать этот подход для тестирования подключенных компонентов. Я использовал этот подход для написания тестов, которые обновляют состояние redux и отображают подключенные компоненты.
Во-первых, вы переопределяете метод RTL render
:
// test-utils.js
import React from 'react'
import { render as rtlRender } from '@testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
// Import your own reducer
import reducer from '../reducer'
function render(
ui,
{
initialState,
store = createStore(reducer, initialState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '@testing-library/react'
// override render method
export { render }
Затем вы ссылаетесь на этот новый метод рендеринга вместо RTL напрямую. Вы также можете указать начальное состояние для своего теста.
import React from 'react'
// We're using our own custom render function and not RTL's render
// our custom utils also re-export everything from RTL
// so we can import fireEvent and screen here as well
import { render, fireEvent, screen } from '../../test-utils'
import App from '../../containers/App'
it('Renders the connected app with initialState', () => {
render(<App />, { initialState: { user: 'Redux User' } })
expect(screen.getByText(/redux user/i)).toBeInTheDocument()
})
(Весь код скопирован из redux.js.org .)
Комментарии:
1. Я пытаюсь сделать это в typescript, однако я не могу найти правильные типы для использования test-utils.js досье. Вы пробовали это в typescript?
Ответ №2:
У меня была та же проблема, что и у вас, но благодаря @srk за ссылку на документы Redux и документы библиотеки тестирования React я нашел довольно хорошее решение, которое сработало для меня с TypeScript:
// store.ts - just for better understanding
export const store = configureStore({
reducer: { user: userReducer },
})
export type RootState = ReturnType<typeof store.getState>
// test-utils.ts
import React, { ReactElement } from 'react'
import { Provider } from 'react-redux'
import { render as rtlRender, RenderOptions } from '@testing-library/react'
import {
configureStore,
EmptyObject,
EnhancedStore,
PreloadedState,
} from '@reduxjs/toolkit'
// import your reducers
import userReducer from 'features/user/user.slice'
import type { RootState } from 'app/store'
// ReducerTypes is just a grouping of each slice type,
// in this example i'm just passing down a User Reducer/State.
// With this, you can define the type for your store.
// The type of a configureStore() is called EnhancedStore,
// which in turn receives the store state as a generic (the same from store.getState()).
type ReducerTypes = Pick<RootState, 'user'>
type TStore = EnhancedStore<ReducerTypes>
type CustomRenderOptions = {
preloadedState?: PreloadedState<ReducerTypes amp; EmptyObject>
store?: TStore
} amp; Omit<RenderOptions, 'wrapper'>
function render(ui: ReactElement, options?: CustomRenderOptions) {
const { preloadedState } = options || {}
const store =
options?.store ||
configureStore({
reducer: {
user: userReducer,
},
preloadedState,
})
function Wrapper({ children }: { children: React.ReactNode }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...options })
}
// re-export everything
export * from '@testing-library/react'
// override render method
export { render }
Затем вам просто нужно передать объект со свойством preloadedState в качестве второго параметра для вашего рендеринга; вы даже можете определить новое хранилище внутри рендеринга, если хотите, со свойством «store».
describe('[Component] Home', () => {
it('User not logged', () => {
const component = render(<Home />)
expect(component.getByText(/User is: undefined/i)).toBeInTheDocument()
})
it('User logged in', () => {
const component = render(<Home />, {
preloadedState: { user: { name: 'John' /* ...other user stuff */ } },
})
expect(component.getByText(/User is: John/i)).toBeInTheDocument()
})
})
Ответ №3:
Я не мог найти где-либо еще, чтобы вставить свои выводы относительно redux toolkit и redux-mock-store.
Чтобы отправлять асинхронные запросы и результаты тестирования, вы можете указать тип отправки при создании макета хранилища.
import configureStore from 'redux-mock-store';
import { getDefaultMiddleware } from '@reduxjs/toolkit'
const middlewares = getDefaultMiddleware();
const mockStore = createMockStore<IRootState, AppDispatch>(middlewares);
describe('my thunk action', () => {
const store = mockStore();
test('calls my service', async() => {
await store.dispatch(myThunk({ id: 32 }));
expect(myService).toBeCalledWith({ id: 32 });
});
test('contains foo bar actions', async() => {
await store.dispatch(myThunk({ id: 32 }));
expect(store.getActions()).toEqual(....);
});
});