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

Cod*_*gle 7 unit-testing reactjs jestjs redux react-testing-library

我正在尝试了解 React 和 Redux 测试的过程,我正在使用测试库来使用dom 节点查询来测试我的项目,但我仍然对在 React 项目中测试 Redux 实现的方式感到困惑:

我创建了一个自定义渲染函数,而不是反应测试库中的普通渲染方法

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 }
Run Code Online (Sandbox Code Playgroud)

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();
   })
})
Run Code Online (Sandbox Code Playgroud)

例如,这是我在应用程序组件中调用的操作创建者

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'
            })
        })
}
Run Code Online (Sandbox Code Playgroud)

这是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,
});
Run Code Online (Sandbox Code Playgroud)

我不确定像这样使用initialState是否是正确的方法,因为我没有找到任何其他方法来在我的测试用例中实现,并且当我尝试测试加载消息是否会消失后,我遇到了问题使用获取的数据waitForElementToBeRemoved,因为我总是收到超时错误,表明loading永远不会像实际应用程序中那样出现错误!

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

ale*_*ero 5

App.js如果您想要根据结果测试 is的行为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 }]})
    })
  })
});
Run Code Online (Sandbox Code Playgroud)

保持关注点分离很重要,Foo组件只需要关心它根据道具的行为方式。如果组件有像 a 这样的副作用,fetch则模拟fetch返回不同的场景并相应地测试它们。