https://cypress.io logo
Using "rerender" after "mount" with Redux Provider
# i-need-help
v
I have a custom
mount
command that wraps my React components with my Redux Provider which works just fine - it's the same as what is explained in this guide https://docs.cypress.io/guides/component-testing/react/examples#Redux. I have a component which compares it's previous props to it's current props, and so in my component test I need to rerender it from one boolean state to the other; To do this I am making use of the
rerender
method like this:
Copy code
it("temporarily displays a 'saved' state when the 'saving' prop changes from true to false", () => {
  const WrappedButton = ({ ...props }) => (
    <Button {...props}>Click me</Button>
  );

  cy.mount(<WrappedButton saving={true} />).then(({ rerender }) => {
    cy.get("[data-test='button-loading']").should("exist");
    cy.get("[data-test='button-saved']").should("not.exist");

    rerender(<WrappedButton saving={false} />);

    cy.get("[data-test='button-loading']").should("not.exist");
    cy.get("[data-test='button-saved']").should("exist");
  });
});
Because my
Button
component connects to the Redux store, I'm getting the following error in Cypress when it gets to the
rerender…
line of code.
Copy code
(uncaught exception)Invariant Violation: Could not find "store" in the context of "Connect(Button)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(Button) in connect options.
Does anyone have any ideas how to solve this? If I refactor my Button component so that it doesn't connect to the Redux store everything works as expected, but in this scenario that isn't a viable solution. I would expect any rerendered component to still be wrapped in the Redux Provider.
h
I can think of two options: - Update your custom mount wrapper so that it also applies your Redux provider to rerenders (rough code below) - Use a little testing component in your test, maybe a second button for "finish saving", which would be a more realistic test in my opinion. Instead of calling
rerender
, do something like
cy.get("[data-test='button-finish-saving']").click()
to change some state variable from
true
to
false
, causing a re-render naturally. Rough code for wrapping re-renders (have not tested this):
Copy code
Cypress.Commands.add('mount', (component, options = {}) => {
  // Use the default store if one is not provided
  const { reduxStore = getStore(), ...mountOptions } = options

  const wrapped = <Provider store={reduxStore}>{component}</Provider>

  const { rerender, ...rest } = mount(wrapped, mountOptions)

  return {
    rest,
    rerender: (reactNode) => rerender(<Provider store={reduxStore}>{reactNode}</Provider>);
  }
})
v
Thanks for taking the time to explain these options. I can confirm that the second option works and it wasn't something I considered so that's really helpful. I must admit though, I do prefer the first option if it is possible, as creating a mini-component with additional elements to do something that I imagine is quite common could get tiresome (and seems to defeat the purpose of the
rerender
method). I have tried the first option using your rough code but unfortunately it does't work and I'm not smart enough to fix it…
Copy code
Cypress detected that you invoked one or more cy commands in a custom command but returned a different value.
The custom command was:
  > cy.mount()
The return value was:
  > {rest: {specwindow: <window>, chainerid: ch-http://localhost:8082-130}, rerender: function(){}}
Because cy commands are asynchronous and are queued to be run later, it doesn't make sense to return anything else.
Do you have any other ideas?
h
Ah yeah, that wasn't very smart of me. Try this:
Copy code
Cypress.Commands.add('mount', (component, options = {}) => {
  // Use the default store if one is not provided
  const { reduxStore = getStore(), ...mountOptions } = options

  const wrapped = <Provider store={reduxStore}>{component}</Provider>

  return mount(wrapped, mountOptions).then(({ rerender, ...rest }) => ({
    ...rest,
    rerender: (reactNode) => rerender(<Provider store={reduxStore}>{reactNode}</Provider>), 
  }));
})
I forgot that
mount
was a command since it wasn't prefixed with
cy
in that example
I edited it a second ago, I missed a
...
v
That's the one! That works a treat, thanks very much
6 Views