How to test components using Mobx stores with Jest - reactjs

I'm trying to test my React components using Mobx stores with Jest and React-testing-library.
The problem is that I have no clues on how to inject my stores for the test.
Here is my simplified codes.
StaffInfo.js(component)
import React, { useState } from "react";
import { observer, inject } from "mobx-react";
const StaffInfo = props => {
const store = props.instituteStore;
const [staffs, setStaffs] = useState(store.staffs);
return (
<div>
....
</div>
);
}
export default inject(rootStore => ({
instituteStore : rootStore.instituteStore
}))(observer(StaffInfo));
index.js(Root store)
import LoginStore from "./LoginStore";
import InstituteStore from "./InstituteStore";
class RootStore {
constructor(){
this.loginStore = new LoginStore (this);
this.instituteStore = new InstituteStore(this);
}
}
export default RootStore;
InstituteStore.js(target store)
import { observable, action } from "mobx";
class InstituteStore {
constructor(root){
this.root = root;
}
#observable
staffs = [];
}
export default InstituteStore;
StaffInfo.test.js(test file)
import React from "react";
import ReactDom from "react-dom";
import { MemoryRouter } from "react-router-dom";
import { Provider } from "mobx-react";
import StaffInfo from "./StaffInfo";
import InstituteStore from "../stores/InstituteStore";
describe("Staff Component testing", () => {
test("should be rendered without crashing", () => {
const div = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/staff"]}>
<StaffInfo instituteStore={RootStore.instituteStore} />
</MemoryRouter>,
div
);
ReactDOM.unmountComponentAtNode(div);
});
});
As soon as running this test file, the error messages are like :
TypeError : Cannot read property 'staffs' of undefined
Please tell me which parts of the codes are wrong.
Thanks so much in advance!

Mobx-react's Inject is used to insert stores to the deep child component. These stars are provided by the context-based API Provider.
so wherever you are providing the stores to the child components use something like.
import rootStore from 'path_to_rootStore'
<Provider rootStore={rootStore}>
...
...
<App/>
...
...
<.Provider>

Thanks to #uneet7:
Legend! Finally someone gave a sensible answer :D
This is what My component looks like and
#inject('routing', 'navigationStore')
#observer
export default class PageTitle extends React.Component<*> {...}
And this is how I made it work:
let view = mount(
<Provider {...getStores()}>
<UserPage notificationStore={notificationStore} routing={routing} />
</Provider>
);
So the UserPage has components (many) and one of those components has PageTitle component. Obviously PageTitle has the #inject on it. It doesn't matter, as Provider HOC will provide stores via inject function to the component props.

Related

wrap the root component in a <Provider>,or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect

I want to test component by unit test , I don't know how to render the component , it makes an error. I can use only "React-testing-library"
Error : Could not find "store" in the context of "Connect(BaseSignUp)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(BaseSignUp) in connect options.
As your component uses your Redux state, you need to wrap it with a Provider.
For example like this
const Wrapper = ({ children }) => (
// you could just use your normal Redux store or create one just for the test
<Provider store={store}>{children}</Provider>
);
render(<BaseSignup />, { wrapper: Wrapper });
You need to create a different generic render wrapper to resolve these issue as majorly all the component ll require the store access
For eg: I have created this wrapper and exported it with name as rtlRender
import { render } from '#testing-library/react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Provider } from 'react-redux'
import ReduxStore from "../redux/store";
const queryClient = new QueryClient();
const rtlRender = component => render(<Provider store={ReduxStore.store}>
<QueryClientProvider client={queryClient}>
{component}
</QueryClientProvider>
</Provider>)
export default rtlRender
and now in my test file I use this rtlRender
import { screen } from '#testing-library/react'
import NewUsers from './NewUsers'
import rtlRender from '../../../utils/rtlRender'
describe('New User', () => {
test('Proper Text', () => {
rtlRender(<NewUsers userDetails={[]} />)
const addUserHeading = screen.getByText('Add user')
expect(addUserHeading).toBeInTheDocument();
})
})

How to render a notification from a global function - React

I'm new to React and I am trying to utilize notistack for ReactJs and I would like to display the notification by calling a helper function but I'm not quite sure how to do that. Here is the standard code required to use the component:
App component:
import { SnackbarProvider } from 'notistack';
<SnackbarProvider maxSnack={3}>
<App />
</SnackbarProvider>
Component that displays the notification:
import { withSnackbar } from 'notistack';
class MyComponent extends Component {
handleNetworkRequest = () => {
fetchSomeData()
.then(() => this.props.enqueueSnackbar('Successfully fetched the data.'))
.catch(() => this.props.enqueueSnackbar('Failed fetching data.'));
};
render(){
//...
};
};
export default withSnackbar(MyComponent);
I would like to place the enqueueSnackbar('my notification message') inside a class or some kind of helper function so that I can call the helper function from anywhere in the react app to display a message without having to wrap the export of a component with withSnackbar(MyComponent);. How can this be done?
I would achieve this via Context API like so:
create a context object which holds the enqueueSnackbar function
Then pass it from the uppermost App comp or any other parent comp
Access it anywhere inside any child component and invoke it as needed
Some pseduo code:
// context.js
import React from 'react';
import { useSnackbar } from 'notistack';
const { enqueueSnackbar } = useSnackbar();
const snackbarContext = React.createContext({ enqueueSnackbar });
export default snackbarContext;
Then wrap a parent component in the tree with this context's provider like so:
//parent.js
import SnackbarContext from './context.js'
const App = () => {
return (
<SnackbarContext.Provider>
<SomeParentComponent />
</SnackbarContext.Provider>
);
}
Now it can be used inside a dummy child component like so:
// child.js
import React, {useContext} from 'react'
import SnackbarContext from './context.js'
const DummyChild = ()=>{
const {enqueueSnackbar} = useContext(SnackbarContext);
return (
<div>
<h1>Dummy Component with snackbar invocation</h1>
<button onClick={() => enqueueSnackbar('Wohoooo')}>Show Snackbar</button>
</div>
)
}

react js react-router-dom stores not pass by providers

I'm using react js with mobx and I'm trying to pass stores in providers and use it but,it seems It's not pass by the providers and I don't have access to it.
in addition when I'm trying to inject the UserStore, the web app is failed and throw an error that UserStore is not available
import { Switch, Route} from 'react-router-dom';
import React, {Component} from 'react';
import {Router} from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
import {Provider} from 'mobx-react'
import { TodoStore,UserStore, ModalsStore} from '../stores'
import App from './App';
import {Login} from '../screens'
const stores = { UserStore}
const browserHistory = createBrowserHistory();
export default class Root extends Component {
render() {
return (
<Provider stores={stores}>
<Router history={browserHistory}>
<Switch>
<Route exact path='/login' component={Login}/>
<Route component={App}/>
</Switch>
</Router>
</Provider>
)
}
}
piece of my App component
#observer
export default class App extends Component {
constructor(props){
super(props);
console.log('appProps',props)
}
render() {
...........
}
UserStore
import {observable,action} from 'mobx'
class UserStore {
#observable token = false
#observable first_name = '';
#observable last_name = ''
#action setUser(data) {
this.token = data.token;
this.first_name = data.first_name;
this.last_name = data.last_name;
}
#action updateUser(data) {
this.first_name = data.first_name;
this.last_name = data.last_name;
}
#action setToken(token){
this.token = token;
}
}
const singelton = new UserStore()
export default singelton
I'm trying to use the userStore and have access but in console i get
You have to #inject('stores') in your App class.
Like this:
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
#inject('stores')
#observer
export default class App extends Component {
render() {
console.log(this.props.stores);
return (
<div>{ /* your components */}</div>
);
}
}
Basically for every class, if you want the store in the props, you have to use inject.
Personally, I prefer import stores from './UserStore' without Provider and inject.
In this way, you can access the store directly, and set any observable inside store the same way as setState.
The code below is the MobX way to use singleton store with observer and observable without using setState():
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import stores from './userStore';
#observer
export default class App extends Component {
render() {
return (
<div>
<input value={stores.first_name} onChange={this.onChangeHandler}/>
</div>
);
}
onChangeHandler = e => {
// MobX will setState and trigger the React re-render for you
stores.first_name = e.target.value;
}
}

React + Redux - Call a method of a component wrapped for the Redux Provider

Whats'up,
I am trying to test some react components that uses redux.
The default behavior should load by a rest call a list of options in a select input. This call is on the method componentDidMount() in my component.
The component works fine, but I cannot simulate the same behavior in my tests.
I cannot call the method componentDidMount() from my instance wrapped by Provider.
Can anyone help me with this
import React from 'react'
import {expect} from 'chai'
import {mount, shallow} from 'enzyme'
import sinon from 'sinon'
import { reducer as formReducer } from 'redux-form'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import ConnectedComponent from '../../../src/components/Component'
describe('Component <Component />', () => {
let store = createStore(combineReducers({ form: formReducer }))
let wrapper = mount(<Provider store={store}><ConnectedComponent /></Provider>)
// this call does not works
wrapper.instance().componentDidMount()
it('should load select input on component mount', () => {
expect(wrapper.find('select option')).to.have.length(12)
})
})
I was able to do like the following :
import React from 'react';
import {connect} from "react-redux";
export class Mock extends React.Component {
constructor(props) {
super(props);
}
myMethod() {
return 123
}
render() {
return (
<div>Test</div>
)
}
}
Mock = connect()(Mock);
export default Mock;
Jest test snippet :
const wrapper = mount(
<Provider store={store}>
<Mock/>
</Provider>
)
let result = wrapper.find(Mock).children().instance().myMethod();
expect(result).toEqual(123);
hope that helps someone!

TypeError: dispatch is not a function when testing with react-create-app jest and enzyme

I'm trying to setup testing on a new project created with react-create-app. Which now seems to be using React 16 and Jest 3 (which supposedly had some breaking changes, or maybe that was enzime). I'm getting an error similar to this post TypeError: dispatch is not a function when I try to test a method using JEST
TypeError: dispatch is not a function
at App.componentDidMount (src/components/App.js:21:68)
import React from 'react';
import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import { App } from '../components/App';
import configureStore from '../state/store/configureStore';
window.store = configureStore({
slider: {
mainImageIndex: 0,
pageNum: 1,
perPage: 4,
},
});
const appTest = (
<Provider store={window.store}>
<App />
</Provider>
);
describe('App', () => {
it('should render without crashing', () => {
mount(appTest);
});
});
Originally I just tried to do this:
import React from 'react';
import { mount } from 'enzyme';
import { App } from '../components/App';
describe('App', () => {
it('should render without crashing', () => {
mount(<App />);
});
});
Which threw this error
Invariant Violation: Could not find "store" in either the context or props of "Connect(Form(SearchForm))". Either wrap the root component in a , or explicitly pass "store" as a prop
Code for App.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { searchPhotos } from '../state/actions/searchPhotos';
import { setMainImageIndex, setFirstPage } from '../state/actions/slider';
import Slider from './Slider';
import SearchForm from './SearchForm';
import Error from './Error';
import '../styles/App.css';
export class App extends Component {
componentDidMount() {
const { dispatch } = this.props;
dispatch(searchPhotos(window.store));
}
searchPhotosSubmit = () => {
const { dispatch } = this.props;
dispatch(setFirstPage());
dispatch(setMainImageIndex(0));
dispatch(searchPhotos(window.store));
}
render() {
const { fetchError } = this.props;
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Flickr Slider in React.js</h1>
<SearchForm onSubmit={this.searchPhotosSubmit} />
</header>
{!fetchError ? <Slider /> : <Error />}
</div>
);
}
}
export default connect(state => ({
fetchError: state.fetchError,
form: state.form,
slider: state.slider,
}))(App);
Please not that you export both presentational component (as named export) and container component (as default export) in App.js. Then in your tests you import and use the presentational component using:
import { App } from '../components/App';
but you should import connected container component instead using:
import App from '../components/App'; // IMPORTANT! - no braces around `App`
Since you're using component that is not connected to Redux store dispatch prop is not injected as prop. Just use correct import and it should work.
For more details about importing default and named exports please check this doc. About presentational and container components you can read here.

Resources