在每次测试之前手动修改 initialState 并将其传递给商店?
Posted
技术标签:
【中文标题】在每次测试之前手动修改 initialState 并将其传递给商店?【英文标题】:manually modifying initialState and pass it to the store before every test? 【发布时间】:2021-01-27 18:51:45 【问题描述】:我正在尝试了解 react 和 redux 测试的过程,我正在使用测试库来使用 dom 节点查询 来测试我的项目,但我仍然对我应该测试的方式感到困惑我的 react 项目中的 redux 实现:
我创建了一个自定义渲染函数,而不是来自 react 测试库的普通渲染方法
import React from 'react'
import render as rtlRender from '@testing-library/react'
import Provider from 'react-redux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares);
//test-utils.js
//creating a custom render function so we can wrap our App with
//react-redux provider
const render = (ui, initialState) =>
const store = mockStore(initialState);
//App wrapper and mocked store passed to it
const Wrapper = ( children ) =>
return <Provider store=store>children</Provider>
return rtlRender(ui, wrapper: Wrapper )
// re-export everything
export * from '@testing-library/react'
// override render method
export render
在 App.test.js 中,我正在手动操作 initialState。这是令人困惑的一部分,我不知道我是否在这里做:
describe('App', () =>
const getByText, getByTestId, findByText, queryByText = screen;
let initialState =
data:
books: [],
error: '',
loading: false
,
//rest of the state
it('should render App correctly with given redux state', () =>
const container = render(<App />, initialState);
expect(container.firstChild).toMatchSnapshot();
expect(getByTestId(/header/)).toHaveTextContent('React Testing')
);
it('displays loading message before data get fetched', () =>
initialState =
...initialState,
data:
...initialState.data,
loading: true
render(<App />, initialState);
expect(getByText(/...loading books/)).toBeInTheDocument();
);
it('display an error message if any thing wrong happened while fetching data', () =>
initialState =
...initialState,
data:
...initialState.data,
error: 'something went wrong'
render(<App />, initialState);
expect(getByText(/something went wrong/)).toBeInTheDocument();
)
)
这是例如我在 App 组件
中调用的动作创建者export const fetchData = () => dispatch =>
dispatch( type: SET_LOADING ); // this set loading to true
return axios.get("https://api.jsonbin.io/b/57d5760ea")
.then(res =>
dispatch(
type: FETCH_DATA, // this set data
payload: res.data.books
);
dispatch( type: STOP_LOADING )
)
.catch(err =>
dispatch(
type: SET_ERROR, // this set errors
payload: 'Something went wrong'
)
)
这是 App.js 组件:
function App( fetchData, data: loading, error, books )
useEffect(() =>
fetchData()
, []);
return (
<div className="App">
<header data-testid="header">
<h2>React Testing</h2>
<Bag />
</header>
error ? error :
!loading ? <Bookstore books=books /> : <span data-testid='loading-message'>...loading books</span>
</div>
);
const mapStateToProps = state => (
data: state.data,
);
我不确定使用这样的 initialState 是否是正确的方法,因为我没有在我的测试用例中找到任何其他实现方法,并且当我尝试测试是否加载消息时遇到了问题使用waitForElementToBeRemoved
获取数据后将消失,因为我总是收到超时错误,指示loading
永远不会像在实际应用中那样变为假!
像这样使用initialState是对还是错,或者可以以其他方式使用是正确的??
【问题讨论】:
您对 initialState 的关注点到底是什么?如果您按照减速器的预期方式在测试中设置它,那么就可以了。否则不是。这是不可能的,因为你没有发布 Redux 部分。 表明加载永远不会像在实际应用中那样为假 - 这可能表明他们以错误的方式使用它。您究竟尝试了 waitForElementToBeRemoved 什么?请提供实际代码。 【参考方案1】:如果您想要测试的是 App.js
的行为取决于 fetch
结果,那么我会以不同的方式处理它。
import fetchData from './fetchDataLocation';
jest.mock('./fetchDataLocation', () => (
fetchData: jest.fn()
))
jest.mock('./Error', () => jest.fn(() => 'Error'));
jest.mock('./Loading', () => jest.fn(() => 'Loading'));
jest.mock('./Bookstore', () => jest.fn(() => 'Bookstore'));
describe('App', () =>
describe('with error', () =>
beforeEach(() =>
Error.mockClear();
Loading.mockClear();
fetchData.mockImplementation(() => Promise.reject('Error'));
)
test('renders loading component', () =>
const container = render(<App />);
expect(Loading).toBeCalled(); // or toBeCalledTimes(1) or lastCalledWith(XYZ) if you want to test the props
)
test('renders error component', () =>
const container = render(<App />);
expect(Error).toBeCalled();
)
)
describe('with data', () =>
beforeEach(() =>
Loading.mockClear();
Bookstore.mockClear();
fetchData.mockImplementation(() => Promise.resolve([ id: 2 ]));
)
test('renders loading component', () =>
const container = render(<App />);
expect(Loading).toBeCalled(); // or toBeCalledTimes(1) or lastCalledWith(XYZ) if you want to test the props
)
test('renders bookstore component', () =>
const container = render(<App />);
expect(Bookstore).lastCalledWith( books: [ id: 2 ])
)
)
);
保持关注点分离很重要,Foo
组件只需要关心它的行为取决于道具。如果组件具有像 fetch
这样的副作用,则模拟 fetch
以返回不同的场景并进行相应的测试。
【讨论】:
以上是关于在每次测试之前手动修改 initialState 并将其传递给商店?的主要内容,如果未能解决你的问题,请参考以下文章