I have the following component wrapped in react-i18next and a mobx-react provider:
import React from "react";
import s from "styled-components";
import { observer, inject } from "mobx-react";
import { translate } from "react-i18next";
#inject("uiStore", "fieldsStore")
#observer
export class HeaderCell extends React.Component {
render() {
const { t } = this.props;
return (
<InputSyle>
<div className="rootCell" />
</InputSyle>
);
}
}
const InputSyle = s.tr`
background-color: red;
`;
export default translate()(HeaderCell);
I am trying to write a simplet test to assert that the .rootCell div gets rendered:
import React from "react";
import { Provider } from "mobx-react";
import Enzyme from "enzyme";
import { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-15";
import HeaderCell from "./HeaderCell";
import { I18nextProvider } from "react-i18next";
import i18n from "../../../../utils/i18n";
const stores = {
uiStore: {},
fieldsStore: {}
};
Enzyme.configure({ adapter: new Adapter() });
describe("HeaderCell.js", () => {
it("renders a single .rootCell div", () => {
const wrapper = mount(
<I18nextProvider i18n={i18n}>
<Provider {...stores}>
<HeaderCell />
</Provider>
</I18nextProvider>
);
expect(wrapper.find(".rootCell")).toHaveLength(1);
});
});
But instead of the test passing I get the following console output:
FAIL
src/components/Sidebar/ParcelsGrid/HeaderCell/HeaderCell.test.js ●
HeaderCell.js › renders a single .rootCell div
expect(received).toHaveLength(length)
Expected value to have length:
1
Received:
{"length": 0, Symbol(enzyme.__unrendered__): null, Symbol(enzyme.__renderer__): {"batchedUpdates": [Function
batchedUpdates], "getNode": [Function getNode], "render": [Function
render], "simulateEvent": [Function simulateEvent], "unmount":
[Function unmount]}, Symbol(enzyme.root): {"length": 1,
Symbol(enzyme.unrendered): , Symbol(enzyme.renderer):
[Object], Symbol(enzyme.root): [Circular],
Symbol(enzyme.node): [Object], Symbol(enzyme.nodes): [Array],
Symbol(enzyme.options): [Object]}, Symbol(enzyme.node):
undefined, Symbol(enzyme.nodes): Array [],
Symbol(enzyme.options): {"adapter": [Object]}}
received.length:
0
at Object.it (src/components/Sidebar/ParcelsGrid/HeaderCell/HeaderCell.test.js:30:39)
at new Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
From the message I am assuming that my component is not getting rendered, but I have no idea why.
I am using "custom-react-scripts": "^0.2.0", "enzyme": "^3.2.0",
"enzyme-adapter-react-15": "^1.0.5", "react-i18next": "^6.0.6".
I would say that the test code is correct, but not really sure? All the wrappers also seem to be working OK as there are no console errors.
Related
I have the following a.jsx file which I am trying to test:
import React, { Component } from "react";
import { SideNavigation } from "#xxx/react-components-xxx";
import { withTranslation } from "react-i18next";
class Navigation extends Component {
constructor(props) {
super(props);
}
render() {
const { t } = this.props;
const ITEMS = [
{
type: "section",
text: t("navigation.abc"),
items: [
{
type: "link",
text: t("navigation.abc"),
href: "#/abc"
},
{ type: "link", text: t("navigation.def"), href: "#/def" }
]
}
];
const HEADER = {
href: "#/",
text: t("navigation.title")
};
return (
<SideNavigation
header={HEADER}
items={ITEMS}
/>
);
}
}
export default withTranslation()(Navigation);
And following is the test case I have written:
import React from "react";
import { shallow } from 'enzyme';
import Navigation from '../src/a';
import { SideNavigation } from '#xxx/react-components-xxx';
describe('Navigation component', () => {
it('should render consistently', () => {
const wrapper = shallow(
<Navigation />
);
console.log(wrapper.render());
expect(wrapper).not.toBeNull();
const sideNav = wrapper.find(SideNavigation);
console.log(sideNav.render());
const sideNavProps = sideNav.props();
console.log(sideNavProps);
});
});
And with this, I get following eror:
Method “type” is meant to be run on 1 node. 0 found instead.
14 |
15 | const sideNav = wrapper.find(SideNavigation);
> 16 | console.log(sideNav.render());
| ^
17 | const sideNavProps = sideNav.props();
18 | console.log(sideNavProps);
19 | });
at ShallowWrapper.single (node_modules/enzyme/src/ShallowWrapper.js:1636:13)
at ShallowWrapper.type (node_modules/enzyme/src/ShallowWrapper.js:1372:17)
at ShallowWrapper.render (node_modules/enzyme/src/ShallowWrapper.js:1106:17)
at Object.<anonymous> (tst/components/Navigation.test.js:16:29)
But if I use mount instead of shallow, everything works fine here. Can someone tell what is the issue here?
It's because of the WithTranslation.
They mention it in their doc
For testing purpose of your component you should export the pure component without extending with the withTranslation hoc and test that:
export MyComponent;
export default withTranslation('ns')(MyComponent);
In the test, test the myComponent export passing a t function mock:
import { MyComponent } from './myComponent';
<MyComponent t={key => key} />
I want to start my React microapp with props I'm passing from Single SPA (customProps). The only way I've figured out is:
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import App from './where/my/root/is.js';
function domElementGetter() {
return document.getElementById("mounting-node")
}
let EnhancedRootComponent = App; /* 1 */
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: EnhancedRootComponent, /* 1 */
domElementGetter,
})
export const bootstrap = [
(args) => {
/* 2 */ EnhancedRootComponent = () => <App myArgs={args.thePropsIWannaPass} />;
return Promise.resolve();
},
reactLifecycles.bootstrap,
];
export const mount = [reactLifecycles.mount];
export const unmount = [reactLifecycles.unmount];
This does work (I can see and use the passed props in my component) but I'm not completely OK with the fact that the root component changes in between calling singleSpaReact (1) and calling bootstrap(2). Would there be side effects to this that I'm not seeing now? Does anyone know a better approach for this?
You have this value inside the props variable without this reassign.
Check this out:
Root-config.js, file responsible for passing prop to microfrontend
import { registerApplication, start } from 'single-spa';
import * as isActive from './activity-functions';
registerApplication('#company/micro2', () => System.import('#company/micro2'), isActive.micro2);
registerApplication('#company/micro1', () => System.import('#company/micro1'), isActive.micro1, { "authToken": "test" });
start();
micro1 Root.tsx
import React from 'react';
export default class Root extends React.Component {
constructor(props: any){
super(props)
}
state = {
hasError: false,
};
componentDidCatch() {
this.setState({ hasError: true });
}
render() {
console.log(this.props)
return (
<div>test</div>
);
}
}
console.log output:
props:
authToken: "test" <---- props which you pass
name: "#company/micro1"
mountParcel: ƒ ()
singleSpa: {…}
__proto__: Object
for more advance usage
const lifecycles = singleSpaReact({
React,
ReactDOM,
loadRootComponent: (props) =>
new Promise((resolve, reject) => resolve(() =>
<Root {...props} test2={'test2'}/>)),
domElementGetter,
});
I'm working with react native and right now I getting the following error when I tried to mount my loading component.
TypeError: undefined is not an object (evaluating 'this.props.navigation.navigate')
Here is my code
import React from 'react';
import SplashScreen from 'react-native-splash-screen';
import Loading from '../../components/loading/Loading';
import { connect } from 'react-redux';
class LoadingContainer extends React.Component {
componentDidMount() {
setTimeout(() => {
SplashScreen.hide();
}, 300);
let initRoute;
if (this.props.auth.user) {
initRoute = 'App';
} else {
initRoute = 'Login';
}
this.props.navigation.navigate(initRoute);
}
render() {
return <Loading navigation={this.props.navigation} />;
}
}
const mapStateToProps = state => ({
auth: state.auth,
});
export default connect(mapStateToProps, null)(LoadingContainer);
Is the first time I getting this error while using react-navigation, I'm using the version 2.11.2 of react navigation
Why don't you use react-navigation v4.x?
For v2.x, your navigation configuration could be like this
import React from 'react';
import { createStackNavigator } from 'react-navigation';
import LoadingContainer from '../containers/LoadingContainer/LoadingContainer';
import Login from '../containers/LoginContainer/Login';
const AppStack = createStackNavigator(
{
Loading: LoadingContainer,
Login: Login,
},
{
initialRouteName: 'Loading',
}
);
export default class App extends React.Component {
render() {
return <AppStack />
}
}
I am trying to test nested connected components (Redux connected):
The Main component has Container component and are provided by store wrapper in App.
import React, { Component } from 'react';
import Main from './components/Main';
import './App.css';
import { Provider } from 'react-redux'
import createStore from './redux'
import applyConfigSettings from './config'
// Apply config overrides
applyConfigSettings()
const store = createStore()
class App extends Component {
render() {
return (
<Provider store={store}>
<Main/>
</Provider>
);
}
}
export default App;
Main.js
import React, { Component } from 'react';
import logo from '../logo.svg';
import { connect } from 'react-redux'
import Container from './Container'
export class Main extends Component {
render(){
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to Pomodoro App</h1>
</header>
<Container/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
fetching: state.user.fetching,
}
}
const mapDispatchToProps = (dispatch) => {
return {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Main)
Container.js
import React, { Component } from 'react';
import Grid from 'material-ui/Grid';
import { connect } from 'react-redux'
export class Container extends Component{
render(){
return (
<div className="grid-root">
</div>
)
}
}
const mapStateToProps = (state) => {
return {
fetching: state.user.fetching,
}
}
const mapDispatchToProps = (dispatch) => {
return {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Container)
All this is created using Create-react-app library. I have installed Enzyme too for testing. Here is my test file for Main.test.js
import React from 'react';
import { shallow, mount } from 'enzyme';
import {Main} from '../../components/Main'
import ContainerConnect, {Container} from '../../components/Container';
import configureStore from 'redux-mock-store';
import { Provider, connect} from 'react-redux';
describe('Main', () => {
let wrapper;
let mountWrapper;
it('wraps all the contents in a div with .App class', () => {
wrapper = shallow(<Main />);
expect(wrapper.find('.App').length).toEqual(1);
});
it('wraps content of header in a div with .App-header class', () => {
wrapper = shallow(<Main />);
expect(wrapper.find('.App-header').length).toEqual(1);
});
it('mount', () => {
const middlewares = [];
const mockStore = configureStore(middlewares);
const initialState = {}
const store = mockStore(initialState)
mountWrapper = mount(
<Provider store={store}>
<Main />
</Provider>
);
});
})
I get following error for the last test:
FAIL src/tests/components/Main.test.js
● Console
console.error node_modules/react-dom/cjs/react-dom.development.js:9643
The above error occurred in the <Connect(Container)> component:
in Connect(Container) (at Main.js:16)
in div (at Main.js:11)
in Main (at Main.test.js:31)
in Provider (created by WrapperComponent)
in WrapperComponent
Consider adding an error boundary to your tree to customize error handling behavior.
Visit some link to fb to learn more about error boundaries.
● Main › mount
TypeError: Cannot read property 'fetching' of undefined
at Function.mapStateToProps [as mapToProps] (src/components/Container.js:18:30)
at mapToPropsProxy (node_modules/react-redux/lib/connect/wrapMapToProps.js:54:92)
at Function.detectFactoryAndVerify (node_modules/react-redux/lib/connect/wrapMapToProps.js:63:19)
at mapToPropsProxy (node_modules/react-redux/lib/connect/wrapMapToProps.js:54:46)
at handleFirstCall (node_modules/react-redux/lib/connect/selectorFactory.js:37:18)
at pureFinalPropsSelector (node_modules/react-redux/lib/connect/selectorFactory.js:85:81)
at Object.runComponentSelector [as run] (node_modules/react-redux/lib/components/connectAdvanced.js:43:25)
at Connect.initSelector (node_modules/react-redux/lib/components/connectAdvanced.js:195:23)
at new Connect (node_modules/react-redux/lib/components/connectAdvanced.js:136:15)
at constructClassInstance (node_modules/react-dom/cjs/react-dom.development.js:6801:20)
at updateClassComponent (node_modules/react-dom/cjs/react-dom.development.js:8336:9)
at beginWork (node_modules/react-dom/cjs/react-dom.development.js:8982:16)
at performUnitOfWork (node_modules/react-dom/cjs/react-dom.development.js:11814:16)
at workLoop (node_modules/react-dom/cjs/react-dom.development.js:11843:26)
at renderRoot (node_modules/react-dom/cjs/react-dom.development.js:11874:9)
at performWorkOnRoot (node_modules/react-dom/cjs/react-dom.development.js:12449:24)
at performWork (node_modules/react-dom/cjs/react-dom.development.js:12370:9)
at performSyncWork (node_modules/react-dom/cjs/react-dom.development.js:12347:5)
at requestWork (node_modules/react-dom/cjs/react-dom.development.js:12247:7)
at scheduleWorkImpl (node_modules/react-dom/cjs/react-dom.development.js:12122:13)
at scheduleWork (node_modules/react-dom/cjs/react-dom.development.js:12082:12)
at scheduleRootUpdate (node_modules/react-dom/cjs/react-dom.development.js:12710:5)
at updateContainerAtExpirationTime (node_modules/react-dom/cjs/react-dom.development.js:12738:12)
at Object.updateContainer (node_modules/react-dom/cjs/react-dom.development.js:12765:14)
at ReactRoot.Object.<anonymous>.ReactRoot.render (node_modules/react-dom/cjs/react-dom.development.js:16069:15)
at node_modules/react-dom/cjs/react-dom.development.js:16488:14
at Object.unbatchedUpdates (node_modules/react-dom/cjs/react-dom.development.js:12557:12)
at legacyRenderSubtreeIntoContainer (node_modules/react-dom/cjs/react-dom.development.js:16484:17)
at Object.render (node_modules/react-dom/cjs/react-dom.development.js:16543:12)
at Object.render (node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:218:50)
at new ReactWrapper (node_modules/enzyme/build/ReactWrapper.js:98:16)
at mount (node_modules/enzyme/build/mount.js:19:10)
at Object.it (src/tests/components/Main.test.js:29:42)
at new Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:118:7)
Is it so hard to test nested connected components? I have to go and use more of it going forward in the application as well. And also does this mean that testing button clicks and all is harder to test as well?
Based on what you are trying to test, you should not have to deal with redux at all.
Since you are testing your <Main /> component and whether it renders the header properly, just mock your <Container /> completely:
jest.mock('../../components/Container', () => ()=> <div id="mockContainer">
mockContainer
</div>);
Then you can shallow mount your <Main /> without worrying about redux.
After that, when you start unit testing your <Container />, just unit test the React.Component class and do not bother with the connected Component, testing it is redundant.
In other words, you should be able to test any React app without ever having to mock the redux store. Just mock connected components entirely.
More info here
I'm testing some react components, a basic tests suite just to know if a component is rendering and their childs.
I'm using redux-mock-store to make the store and {mount} enzyme to mount the container in a provider, but even mocking the correct store this error is always fired:
TypeError: Cannot read property 'pathname' of undefined
Here is my very deadly basic test:
import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import App from '../containers/App.container';
describe('App', () => {
let wrapper;
const mockStore = configureStore([]);
const store = mockStore({
router: {
location: { pathname: '/home', query: {}, search: '' },
params: {}
}
});
console.log(store.getState());
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<App />
</Provider>
);
});
it('Should render app and container elements', () => {
expect(wrapper.find('.app').exists()).toBeTruthy();
expect(wrapper.find('.container').exists()).toBeTruthy();
});
it('Should render the navbar', () => {
expect(wrapper.find('nav').exists()).toBeTruthy();
});
});
And the (even more) simple component / container:
import React, { Component } from 'react';
import NavBar from '../components/Navbar';
class App extends Component {
render() {
const { location, logout} = this.props;
console.log(location);
return (
<section className='app'>
<NavBar location={location.pathname} onLogoutClick={logout}/>
<div className='container'>
{this.props.children}
</div>
</section>
);
}
}
export default App;
Container:
import { connect } from 'react-redux';
import { signOut } from '../actions/auth.actions'
import App from '../components/App';
const mapStateToProps = (state, ownProps) => {
return {
location: ownProps.location
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
logout: () => {
dispatch(signOut())
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
I can't figure out the problem of the test, the mockStore is in the correct format:
Any idea?
Update:
Thinking about it, I have no reducer / prop in the rootReducer for the location, but, i just want to pass down through the children components the location object properties that react-redux-router make available in the ownProps argument.
Weird fact: logging the location property in the app returns me the correct object.
In the tests, is always undefined... (as the error shows).
Here is my rootReducer:
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import { routerReducer } from 'react-router-redux';
import authReducer from './auth.reducer';
import analysisReportsReducer from './AnalysisReports.reducer';
import titleAnalysisReducer from './TitleAnalysis.reducer';
import postsReportsReducer from './PostsReports.reducer';
const rootReducer = combineReducers({
form: formReducer,
routing: routerReducer,
auth: authReducer,
analysis: titleAnalysisReducer,
analysis_reports: analysisReportsReducer,
posts: postsReportsReducer
});
export default rootReducer;
It looks like your location object is scoped beneath the router.
Your test may be grabbing the window.location property, which your test suite may not replicate, assuming the test is cli and not in a browser.
Perhaps try:
<NavBar location={this.props.router.location.pathname} onLogoutClick={logout}/>