Tim Frailey
Tim Frailey

Reputation: 61

React/Mobx: Integration Tests with Stores Injected into Child Components

We are trying to write out unit/integration tests for all of our existing React components. We are currently using React with Mobx 4, with tests written mostly with react-testing-library/jest. We did use Enzyme in some areas as well to make use of shallow rendering. Our issue is that as we get to some of our 'pages', or container components, we are getting errors such as "MobX injector: Store 'teamStore' is not available! Make sure it is provided by some Provider"

We've done a bit of digging but couldn't find anything in our searches of similar issues for reference. We do know that this is caused by child components that have stores injected into them directly, and that are called into our container/page.

My question is: Is there any way within the testing frameworks to pass down mock stores created in our container components down to child components? Obviously if we passed the store as a prop from the parent to the child, that solves the issue, but we are trying to avoid modifying the components themselves in any way.

If the above is not possible, do we have any other options without refactoring components to pass down stores as needed rather than injecting directly into child components?


    import React, { Component } from "react";
    import { inject, observer } from "mobx-react";
    import { Container, Grid, Segment } from "semantic-ui-react";
    import ChildComp from "../../components/ChildComp";

    @inject("userStore")
    @observer
    class ParentComponent extends Component {

      render() {
        return (
            <Container className="parent">
                <Segment basic>
                    <h1>Hello</h1>
                    <ChildComp />
                </Segment>
            </Container>
        );
      }
    }

    export default ParentComponent;


    import React, { Component } from "react";
    import { inject, observer } from "mobx-react";
    import { Container, Grid, Segment } from "semantic-ui-react";

    @inject("teamStore")
    @observer
    class ChildComp extends Component {

      render() {
        return (
            <Segment basic>
                <p>How can I help you?</p>
            </Segment>
        );
      }
    }

    export default ChildComp;

Upvotes: 6

Views: 3806

Answers (1)

Maayan Glikser
Maayan Glikser

Reputation: 873

Using jest you can mock parts of mobx to provide your own mock store so instead of running the real inject function you can provide your own inject function instead.

Using that custom inject function you can return a fake store (which needs to match the same interface as the original store).

If you want to pre populate the store with values by importing the mock you created (jest doesn't allow variables on the module/global scope to be used when using jest.mock)

Here is an example code that achieves this (this is untested code written right here on stackoverflow so might needs some tweaks to get right).

jest.mock('mobx-react', () => {
  // get the original reference to mobx-react
  const originalMobx = require.requireActual('mobx-react');

  // create your fake stores, they should have the same interface as the real store
  const mockStores = {
    userStore: new UserStore()
  };

  return {
    ...originalMobx, // allow to import the original properties in react-mobx
    // override the inject decorator to instead return the fake store as a prop    
    inject: (injectName) => (component) => (props) => {
      // render the real component with the additional prop
      return react.createElement(component, {...props, [injectName]: mockStores[injectName] })  
    },
    mockStores // Allows access afterwards via import e.g import { mockStores } from 'mobx-react'
  }
});

Once you mocked the mobx-react inject function you can reference the store to pre populate the values by:

import { mockStores } from 'mobx-react';

test('my test', () => {
  mockStores.userStore.clearUsers();

  // render the component here
})

There is also an alternative solution where you can just wrap the tested component with Provider from mobx-react and supply fake stores.

so the test will initialize them beforehand and pass the down the context.

e.g

test('my comp', () => {
 const userStore = new UserStore();
 const component = shallow(
   <Provider userStore={userStore}>
     <MyComponent />
   </Provider>
 )
});

Upvotes: 5

Related Questions