React-testing-library with connected react router together
Asked Answered
P

1

11

I am trying to test a workflow with a React app. When all fields are filled within a workflow step, the user is able to click the "next" button. This action registers a state in a reducer and changes the URL to go to the next workflow step.

[According to the RTL documentation] 1, I wrap my component under test in a store provider and a connected router using this function:

export const renderWithRedux = (ui: JSX.Element, initialState: any = {}, route: string = "/") => {
  // @ts-ignore
  const root = reducer({}, { type: "@@INIT" })
  const state = mergeDeepRight(root, initialState)

  const store = createStoreWithMiddleWare(reducer, state)
  const history = createMemoryHistory({ initialEntries: [route]})

  const Wrapper = ({ children }: any) => (
    <Provider store={store}>
      <ConnectedRouter history={history}>{children}</ConnectedRouter>
    </Provider>
  )
  return {
    ...render(ui, { wrapper: Wrapper }),
    // adding `store` to the returned utilities to allow us
    // to reference it in our tests (just try to avoid using
    // this to test implementation details).
    history,
    store
  }
}

Unlike in the documentation, I am using connected-react-router, not react-router-dom, but I've seen some people using connected-react-router with RTL on the web, so I don't think the problem comes from here.

The component under test is wrapped in a withRouter function, and I refresh the URL via the connected react router push function, dispatching via the redux connectfunction:

export default withRouter(
  connect<any, any, any>(mapStateToProps, mapDispatchToProps, mergeProps)(View)
)

Everything work well in production, but the page doesn't refresh when I fire a click event on the "next" button. Here is the code of my test (to make it easier to read for you, I have filled all field and enable the "next" button):

    const {
      container,
      queryByText,
      getAllByPlaceholderText,
      getByText,
      debug,
      getAllByText
    } = renderWithRedux(<Wrapper />, getInitialState(), "/workflow/EXAC")

    await waitForElement(
      () => [getByText("supplierColumnHeader"), getByText("nextButton")],
      { container }
    )

    fireEvent.click(getByText("nextButton"))

    await waitForElement(
      () => [getByText("INTERNAL PARENT"), getByText("EXTERNAL PARENT")],
      { container }
    )

Any clue as to what is going wrong here?

Poulin answered 11/12, 2019 at 10:18 Comment(1)
Hey @ViGrad, found a solution for this?Warr
E
0

There's insufficient information in your question to figure out exactly what's going on, but this is a big clue:

When all fields are filled withing a workflow step, user is able to click to the "next" button. This action registers a state in a reducer and changes the URL to go to the next workflow step.

Clicking the button changes the URL. For this to work, the corresponding Route needs to be rendered within the ConnectedRouter in a functional manner, ideally, in the same manner used in the application, which works. Based on the following, I'm guessing that you're not rendering the Route required for the expected page to be rendered:

The component under test is wrapped in a withRouter function, and I refresh the URL via the connected react router push function, dispatching via the redux connectfunction

From what I can tell, your renderWithRedux utility is rendering the provided element within a redux Provider and a ConnectedRouter. This means the element you provide:

  • should be working in context of the redux store, so dispatching actions should work, assuming that the store used in your tests is functionally the same as the one used in your working application.
  • should respond to route changes, assuming that you're also rendering the Route that corresponds to the URL change under test, ideally in the same way it does in your working application.

If whatever you're rendering isn't actually rendering the Route that corresponds to your URL change, nothing will happen in the DOM. Either the history object returned from your render utility will be updated with an additional entry or JSDOM's window.location will be updated.

You can assert on changes to history or window.location if need be, but that is less than ideal, as testing library recommends testing the user experience, rather than technical/implementation details:

fireEvent.click(getByText("nextButton"));
expect(history.location.pathname).toBe('/step-two');
Eikon answered 9/6, 2023 at 17:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.