在每次测试之前手动修改 initialState 并将其传递给商店?

manually modifying initialState and pass it to the store before every test?

我试图了解反应和 redux 测试的过程,我正在使用测试库来使用 dom 节点查询 来测试我的项目,但是我我仍然对我应该在我的反应项目中测试 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 永远不会像在实际应用程序中那样变为 false!

像这样使用 initialState 是对还是错还是可以以其他方式使用才是正确的??

如果您想要测试 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 到 return 不同的场景并相应地测试它们。