34

I have a component SampleComponent that mounts another "connected component" (i.e. container). When I try to test SampleComponent by mounting (since I need the componentDidMount), I get the error:

Invariant Violation: Could not find "store" in either the context or props of "Connect(ContainerComponent)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(ContainerComponent)".

What's the best way of testing this?

Matt Hampel
  • 5,088
  • 12
  • 52
  • 78
Detuned
  • 3,652
  • 4
  • 27
  • 54
  • Since Enzyme v3, shallow calls ComponentDidMount https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md FYI – Dan Garland Nov 16 '18 at 11:23

6 Answers6

43

Enzyme's mount takes optional parameters. The two that are necessary for what you need are

options.context: (Object [optional]): Context to be passed into the component

options.childContextTypes: (Object [optional]): Merged contextTypes for all children of the wrapper You would mount SampleComponent with an options object like so:

const store = { 
  subscribe: () => {},
  dispatch: () => {},
  getState: () => ({ ... whatever state you need to pass in ... })
}
const options = {
  context: { store }, 
  childContextTypes: { store: React.PropTypes.object.isRequired } 
}

const _wrapper = mount(<SampleComponent {...defaultProps} />, options)

Now your SampleComponent will pass the context you provided down to the connected component.

zentuit
  • 716
  • 1
  • 6
  • 4
  • 3
    This is perfect! While the accepted answer works most of the time, the downside is that you lose the ability to use the mount api to its fullest capacity. For example, using the accepted answer of wrapping the component in a `Provider` will not allow one to use the `wrapper.state()` api. This solution will give you the full range of methods for your wrapper. – neurosnap Dec 21 '16 at 03:36
  • 1
    This is a better answer than the accepted answer, for the reasons stated above (i.e. your mounted wrapper is not actually the component you are trying to test), and also because then you can use a mock store instead of your actual store, leaving all of redux out of the equation. – GTF Jan 19 '17 at 16:16
  • 3
    this optional argument is not in docs , how did you find it? in the code ? – ashish singh Jan 25 '17 at 10:12
  • 1
    What if `SampleComponent` uses a connected component called `SubComponent`, and we want to check that it renders correctly (integration test, more than unit) using `mount`? You can pass the store as the second parameter to `SampleComponent` but `SubComponent` will have no store. – David Torres Sep 18 '18 at 13:22
11

What I essentially did was bring in my redux store (and Provider) and wrapped it in a utility component as follows:

export const CustomProvider = ({ children }) => {
  return (
    <Provider store={store}>
      {children}
    </Provider>
  );
};

then, I mount the SampleComponent and run tests against it:

it('contains <ChildComponent/> Component', () => {
  const wrapper = mount(
    <CustomProvider>
      <SampleComponent {...defaultProps} />
    </CustomProvider>
  );
  expect(wrapper.find(ChildComponent)).to.have.length(1);
});
Detuned
  • 3,652
  • 4
  • 27
  • 54
  • 1
    I see you are using mount, if I try to replace ``mount`` with ``shallo`` I get an error. do you have encountered that too? – Mehrdad Shokri Aug 17 '16 at 10:53
  • 4
    While this answer works in some cases, it doesn't work when you need to test your component's lifecycle. For example, calling `wrapper.setProps()` will not trigger `componentWillReceiveProps()` on `SampleComponent`. – Slavo Vojacek Jan 16 '17 at 12:15
4

Option 1)

You can wrap the container component with React-Redux's Provider component within your test. So with this approach, you actually reference the store, pass it to the Provider, and compose your component under test inside. The advantage of this approach is you can actually create a custom store for the test. This approach is useful if you want to test the Redux-related portions of your component.

Option 2)

Maybe you don't care about testing the Redux-related pieces. If you're merely interested in testing the component's rendering and local state-related behaviors, you can simply add a named export for the unconnected plain version of your component. And just to clarify when you add the "export" keyword to your class basically you are saying that now the class could be imported in 2 ways either with curly braces {} or not. example:

export class MyComponent extends React.Component{ render(){ ... }}

...

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

later on your test file:

import MyComponent from 'your-path/MyComponent'; // it needs a store because you use "default export" with connect
import {MyComponent} from 'your-path/MyComponent'; // don't need store because you use "export" on top of your class.

I hope helps anyone out there.

Frank N
  • 9,625
  • 4
  • 80
  • 110
darmis
  • 2,879
  • 1
  • 19
  • 21
4

There is also the option to use redux-mock-store.

A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.

The mock store provides the necessary methods on the store object which are required for Redux. You can specify optional middlewares and your app specific initial state.

import configureStore from 'redux-mock-store'

const middlewares = []
const mockStore = configureStore(middlewares)

const initialState = {}
const store = mockStore(initialState)

const wrapper = mount(<SampleComponent store={store}/>)
brass monkey
  • 5,841
  • 10
  • 36
  • 61
3

You can use name export to solve this problem:

You should have:

class SampleComponent extends React.Component{
...
   render(){
       <div></div>
   }
}

export default connect(mapStateToProps, mapDispatchToProps)(SampleComponent)

You can add a export before class:

export class SampleComponent extends React.Component{

and import this component with no redux store:

import { SampleComponent } from 'your-path/SampleComponent';

With this solution you don't need to import store to your test files.

Alessander França
  • 2,697
  • 2
  • 29
  • 52
0

in an attempt to make the use of decorator syntax more testable I made this: https://www.npmjs.com/package/babel-plugin-undecorate

input:

@anyOldClassDecorator
export class AnyOldClass {
  @anyOldMethodDecorator
  method() {
    console.log('hello');   
  }
}

output:

@anyOldClassDecorator
export class AnyOldClass {
  @anyOldMethodDecorator
  method() {
    console.log('hello');   
  }
}

export class __undecorated__AnyOldClass {
  method() {
    console.log('hello');   
  }
}

Hopefully this can provide a solid Option 3!

SirRodge
  • 594
  • 5
  • 8