I am using Mocha, Chai, Nock, Sinon, Webpack for Unit tests.
I used the following link to test my actions, reducers
http://redux.js.org/docs/recipes/WritingTests.html
I was able to test dumb components from other react articles. Currently I am trying to test my smart component and i get errors.
my react-redux component (SmartComponent.js)
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import * as componentActions from '../actions/componentActions'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
export class SmartComponent extends Component {
constructor(props) {
super(props)
this.updateText = this.updateText.bind(this)
}
updateText(event){
this.props.actions.updateText(event.target.value)
}
render() {
return(
<div>
<input type='text' onChange={this.action1} placeholder='type something'
/>
<span>{this.props.inputText}
</div>
)
}
}
function mapStateToProps(state) {
const inputText = state.inputText
return{
inputText
}
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(componentActions, dispatch)
}
}
const SmartComponentContainer = connect(
mapStateToProps,
mapDispatchToProps
)(SmartComponent)
module.exports = SmartComponentContainer
this is my unit test file
import setupDom from '../setup'
import chai from 'chai'
import jsxChai from 'jsx-chai'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import TestUtils from 'react-addons-test-utils'
import {Provider} from 'react-redux'
import {findAllWithType, findWithType, findWithClass} from 'react-shallow-testutils'
import SmartComponentContainer,{SmartComponent} from "../containers/SmartComponent"
chai.use(jsxChai)
let expect = chai.expect
/**
* Mock out the top level Redux store with all the required
* methods and have it return the provided state by default.
* #param {Object} state State to populate in store
* #return {Object} Mock store
*/
function createMockStore(state) {
return {
subscribe: () => {},
dispatch: () => {},
getState: () => {
return {...state};
}
};
}
function setup() {
const storeState={
inputText : ''
}
let renderer = TestUtils.createRenderer()
renderer.render(<SmartComponentContainer store={createMockStore(storeState)} />)
let output = renderer.getRenderOutput()
return {
output,
renderer
}
}
function setUpComponent(){
const props ={}
let renderer = TestUtils.createRenderer()
renderer.render(<SmartComponent {...props} />)
let output = renderer.getRenderOutput()
return {
output,
renderer
}
}
describe('test smart component container',()=>{
it('test input value change',()=>{
const {output,renderer} = setup()
console.log(output)
// when i do the console.log output here , i am getting my actions and state variable but i do not get the html inside render method
})
it('test component',()=>{
const {output,renderer} = setUpComponent()
console.log(output)
})
})
The first test is successful and i prints the output with actions and state variables but not able to get render html printed.
The second test throws an error "TypeError: Cannot read property 'propTypes' of undefined" . I want access to html and have ability test like with normal component
expect(output.props.children[0].type).to.equal('input')
Related
I am trying to test my component which is consuming data from context via HOC.
Here is setup:
Mocked context module /context/__mocks__
const context = { navOpen: false, toggleNav: jest.fn() }
export const AppContext = ({
Consumer(props) {
return props.children(context)
}
})
Higher OrderComponent /context/withAppContext
import React from 'react'
import { AppContext } from './AppContext.js'
/**
* HOC with Context Consumer
* #param {Component} Component
*/
const withAppContext = (Component) => (props) => (
<AppContext.Consumer>
{state => <Component {...props} {...state}/>}
</AppContext.Consumer>
)
export default withAppContext
Component NavToggle
import React from 'react'
import withAppContext from '../../../context/withAppContext'
import css from './navToggle/navToggle.scss'
const NavToggle = ({ toggleNav, navOpen }) => (
<div className={[css.navBtn, navOpen ? css.active : null].join(' ')} onClick={toggleNav}>
<span />
<span />
<span />
</div>
)
export default withAppContext(NavToggle)
And finally Test suite /navToggle/navToggle.test
import React from 'react'
import { mount } from 'enzyme'
beforeEach(() => {
jest.resetModules()
})
jest.mock('../../../../context/AppContext')
describe('<NavToggle/>', () => {
it('Matches snapshot with default context', () => {
const NavToggle = require('../NavToggle')
const component = mount( <NavToggle/> )
expect(component).toMatchSnapshot()
})
})
Test is just to get going, but I am facing this error:
Warning: Failed prop type: Component must be a valid element type!
in WrapperComponent
Which I believe is problem with HOC, should I mock that somehow instead of the AppContext, because technically AppContext is not called directly by NavToggle component but is called in wrapping component.
Thanks in advance for any input.
So I solved it.
There were few issues with my attempt above.
require does not understand default export unless you specify it
mounting blank component returned error
mocking AppContext with __mock__ file caused problem when I wanted to modify context for test
I have solved it following way.
I created helper function mocking AppContext with custom context as parameter
export const defaultContext = { navOpen: false, toggleNav: jest.fn(), closeNav: jest.fn(), path: '/' }
const setMockAppContext = (context = defaultContext) => {
return jest.doMock('../context/AppContext', () => ({
AppContext: {
Consumer: (props) => props.children(context)
}
}))
}
export default setMockAppContext
And then test file ended looking like this
import React from 'react'
import { shallow } from 'enzyme'
import NavToggle from '../NavToggle'
import setMockAppContext, { defaultContext } from '../../../../testUtils/setMockAppContext'
beforeEach(() => {
jest.resetModules()
})
describe('<NavToggle/>', () => {
//...
it('Should have active class if context.navOpen is true', () => {
setMockAppContext({...defaultContext, navOpen: true})
const NavToggle = require('../NavToggle').default //here needed to specify default export
const component = shallow(<NavToggle/>)
expect(component.dive().dive().hasClass('active')).toBe(true) //while shallow, I needed to dive deeper in component because of wrapping HOC
})
//...
})
Another approach would be to export the component twice, once as decorated with HOC and once as clean component and create test on it, just testing behavior with different props. And then test just HOC as unit that it actually passes correct props to any wrapped component.
I wanted to avoid this solution because I didn't want to modify project file(even if it's just one word) just to accommodate the tests.
I try to use Redux with next.js starter project and I installed next-redux-wrapper on the project but I'm not sure where is the root file in this project.
I try to follow the tutorial shown on the next-redux-wrapper but had no success. Nothing change.
Please help me with how to add Redux to the project.
Regards.
Next.js uses the App component to initialize pages. You can override it and control the page initialization.
Although this demo is for next.js it should work for nextjs-starter.
install next-redux-wrapper:
npm install --save next-redux-wrapper
Add _app.js file to ./pages directory:
// pages/_app.js
import React from "react";
import {createStore} from "redux";
import {Provider} from "react-redux";
import App, {Container} from "next/app";
import withRedux from "next-redux-wrapper";
const reducer = (state = {foo: ''}, action) => {
switch (action.type) {
case 'FOO':
return {...state, foo: action.payload};
default:
return state
}
};
/**
* #param {object} initialState
* #param {boolean} options.isServer indicates whether it is a server side or client side
* #param {Request} options.req NodeJS Request object (not set when client applies initialState from server)
* #param {Request} options.res NodeJS Request object (not set when client applies initialState from server)
* #param {boolean} options.debug User-defined debug mode param
* #param {string} options.storeKey This key will be used to preserve store in global namespace for safe HMR
*/
const makeStore = (initialState, options) => {
return createStore(reducer, initialState);
};
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
// we can dispatch from here too
ctx.store.dispatch({type: 'FOO', payload: 'foo'});
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
return {pageProps};
}
render() {
const {Component, pageProps, store} = this.props;
return (
<Container>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</Container>
);
}
}
export default withRedux(makeStore)(MyApp);
And then, actual page components can be simply connected:
This demo how to connect index.js in pages.
import Link from "next/link";
import React from "react";
import {
Container,
Row,
Col,
Button,
Jumbotron,
ListGroup,
ListGroupItem
} from "reactstrap";
import Page from "../components/page";
import Layout from "../components/layout";
import { connect } from "react-redux";
class Default extends Page {
static getInitialProps({ store, isServer, pathname, query }) {
store.dispatch({ type: "FOO", payload: "foo" }); // component will be able to read from store's state when rendered
return { custom: "custom" }; // you can pass some custom props to component from here
}
render() {
return (
<Layout>content...</Layout>
);
}
}
export default connect()(Default);
Refer to the documentation for more information: next-redux-wrapper
First i created simple next.js app with "npx create-next-app"
Then I created general redux setup in a folder called "store".
This is the folder structure
And in pages i have created an _app.js. Code inside it is like this-
Please let me know if anyone needs any help with setup...
this question needs update:
"next": "12.1.4",
"next-redux-wrapper": "^7.0.5",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^2.4.1"
Create Store
import { createStore, applyMiddleware } from "redux";
import { HYDRATE, createWrapper } from "next-redux-wrapper";
import reducers from "./reducers/reducers";
import thunkMiddleware from "redux-thunk";
// middleware is an array
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
const { composeWithDevTools } = require("redux-devtools-extension");
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
// this is main reducer to handle the hydration
const reducer = (state, action) => {
// hydration is a process of filling an object with some data
// this is called when server side request happens
if (action.type === HYDRATE) {
const nextState = {
...state,
...action.payload,
};
return nextState;
} else {
// whenever we deal with static rendering or client side rendering, this will be the case
// reducers is the combinedReducers
return reducers(state, action);
}
};
const initStore = () => {
return createStore(reducer, bindMiddleware([thunkMiddleware]));
};
export const wrapper = createWrapper(initStore);
_app.js
import { wrapper } from "../redux/store";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default wrapper.withRedux(MyApp);
Using it with getServerSideProps
// sample action
import { getRooms } from "../redux/actions/roomActions";
import { wrapper } from "../redux/store";
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
// destructuring context obj
async ({ req, query }) => {
await store.dispatch(getRooms(req, query.page));
}
);
in my react App i'm using redux with redux-thunk.right now i'm getting props in my component but i'm unable to access latest props in my component methodsso i used componentWillReceiveProps to get latest props using nextprops then i'm saving nextprops into my states but the problem here is setState is asynchronous so when i'm fetching particular state in class methods,getting prev state value instead of nextprops value which is saved in state. but when i'm console those state in class methods using setInterval getting latest state value because setState value now saved.below is my code
Action creator
export function pickup(latlng) {
return function(dispatch) {
dispatch({ type: PICKUP_STATE,payload:latlng });
};
}
Reducer
import {
PICKUP_STATE,
PICKUP_ADD,
DROPOFF_STATE
} from '../actions/types';
export default (state={},action) => {
const INITIAL_STATE = {
pickup: '',
pickupAdd:''
};
switch(action.type) {
case PICKUP_STATE:
console.log(action.payload)
return {...state,pickup:action.payload};
case PICKUP_ADD:
return{...state,pickupAdd:action.payload};
case DROPOFF_STATE:
return {...state,dropoff:action.payload}
default:
return state;
}
//return state;
}
component
import {
connect
} from "react-redux";
import * as actions from "../actions"
class Map extends React.Component {
componentWillReceiveProps(nextprops) {
if (nextprops.pickupProps !== undefined) {
this.setState({
pick: nextprops.pickupProps
}, () => {
console.log(this.state.pick);
});
}
}
isPickEmpty(emptyPickState) {
this.props.pickup(emptyPickState);
// setTimeout(() =>{ console.log('sdkjlfjlksd',this.state.pick)
},3000);
console.log(this.state.pick);
}
}
const mapStateToProps = (state) => {
// console.log(state.BookingData.pickup);
return {
pickupProps:state.BookingData.pickup,
pickupAddProps: state.BookingData.pickupAdd
}
}
export default connect(mapStateToProps,actions)(Map);
App Root file
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import "normalize.css/normalize.css"
import "./styles/styles.scss";
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import reduxThunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import AppRouter from './routers/AppRouter';
import reducers from './reducers';
import {AUTH_USER} from "./actions/types";
const middleware = [
reduxThunk,
];
const store = createStore(reducers, composeWithDevTools(
applyMiddleware(...middleware),
// other store enhancers if any
));
const token = localStorage.getItem('token');
if(token){
store.dispatch({type:AUTH_USER});
}
ReactDOM.render(
<Provider store={store}>
<AppRouter />
</Provider>
, document.getElementById('app'));
1- how can i access latest props in my class methods
OR
2- how can i access nextprops setState value in my class methods
OR
3- any best way to solve this situation
please any one help me out from this situation, i'm stuck in from 3 days
If I understand it correctly, you still need to add a maps to dispatch to get the updated states from the store. The action creator still needs to be called and then mount it to your class method using componenetsDidMount
componentDidMount() {
this.props.fetchPickUp();
}
const mapDispatch = dispatch => {
return {
fetchPickUp: () => dispatch(pickUp()),
};
Problem:
I can't display the value from the state of redux, which is delivered by mapStateToProps function to the component.
Project structure:
Create-react-app CLi application built the project.
Inside of the src/ I have the following code structure
Necessary code:
The main page which we are interacting with looks like this:
Underneath it is planned to post the result of the clicking on the buttons.
So how do I bind the redux state and actions to those two components: Calculator and ResultLine?
Let me show the index.js code, where I create the store:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from './reducers/';
import App from './components/App';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.getElementById("root")
);
There are only three actions:
import {CALCULATE, ERASE, PUT_SYMBOL} from "./types";
export const putSymbol = (symbol) => {
return {
type: PUT_SYMBOL,
payload: symbol
}
};
export const calculate = () => {
return {
type: CALCULATE
}
};
export const erase = () => {
return {
type: ERASE
}
};
And in the App.js I pass reducers, which are binded to those actions to the Calculator component:
import React, {Component} from 'react';
import Calculator from './Calculator';
import ResultLine from "./ResultLine";
import {calculate, erase, putSymbol} from "../actions/index";
import {connect} from "react-redux";
class App extends Component {
render() {
return (
<div>
<Calculator
onSymbolClick={this.props.onSymbolClick}
onEqualsClick={this.props.onEqualsClick}
onEraseClick={this.props.onEraseClick}/>
<br/>
<ResultLine result={this.props.result}/>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('mapState', state.calc.line);
return {
result: state.line
}
};
const mapDispatchToProps = {
onSymbolClick: putSymbol,
onEqualsClick: calculate,
onEraseClick: erase
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
And that works fine. Whenever I click the button the state changes, and I observe it in the console log, called in mapStateToProps function.
So I expect, that I can deliver result prop to the Result line easily, and I pass it into the ResultLine component as a parameter. So, let's look at that element:
import React from 'react';
const ResultLine = ({result}) => {
return (
<p>{result}</p>
);
};
export default ResultLine;
And I can see no changes in a result line. Maybe, something wrong with the React/Redux lifecycle management and ResultLine component just does not update on changes in state?
There's an error on mapStateToProps.
Instead of:
const mapStateToProps = (state) => {
return {
result: state.line
}
}
Please use:
const mapStateToProps = (state) => {
return {
result: state.calc.line // calc was missing here
}
}
I've pasted my Component below which is very, very basic. When the Component is mounted, it will basically call the fetchMessage Action, which returns a message from an API. That message will in turn get set as state.feature.message in the mapStateToProps function.
I'm at a complete loss on where to begin testing this Component. I know that I want to test that:
1) The Feature Component is rendered
2) The fetchMessage function in props is called
3) It displays or has the correct message property when rendered using that
I've tried setting my test file up as you can see below, but I just end up with repeated error after error with everything that I try.
Could anyone point me in the right direction with what I'm doing wrong?
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
class Feature extends Component {
static propTypes = {
fetchMessage: PropTypes.func.isRequired,
message: PropTypes.string
}
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div>{this.props.message}</div>
);
}
}
function mapStateToProps(state) {
return { message: state.feature.message };
}
export default connect(mapStateToProps, actions)(Feature);
Test file:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import expect from 'expect';
import { shallow, render, mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import Feature from '../index';
const mockStore = configureStore([thunk]);
describe('<Feature />', () => {
let store;
beforeEach(() => {
store = mockStore({
feature: {
message: 'This is the message'
}
});
});
it('renders a <Feature /> component and calls fetchMessage', () => {
const props = {
fetchMessage: sinon.spy()
};
const wrapper = mount(
<Provider store={store}>
<Feature {...props} />
</Provider>
);
expect(wrapper.find('Feature').length).toEqual(1);
expect(props.fetchMessage.calledOnce).toEqual(true);
});
});
You can use shallow() instead of mount() to test your component. The shallow() method calls the componentWillMount() life-cycle method so there is no reason to use mount(). (Disclaimer: I am not quite well at mount() yet.)
For connected components, you can pass a store object like this:
<connectedFeature {...props} store={store} />
And you should call shallow() method twice to make it work for connected components:
const wrapper = shallow(<connectedFeature {...props} store={store} />).shallow()
Testing Connected React Components
Use separate exports for the connected and unconnected versions of the components.
Export the unconnected component as a named export and the connected as a default export.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
// export the unwrapped component as a named export
export class Feature extends Component {
static propTypes = {
fetchMessage: PropTypes.func.isRequired,
message: PropTypes.string
}
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div>{this.props.message}</div>
);
}
}
function mapStateToProps(state) {
return { message: state.feature.message };
}
// export the wrapped component as a default export
export default connect(mapStateToProps, actions)(Feature);
Remember connected components must be wrapped in a Provider component as shown below.
Whereas unconnected components can be tested in isolation as they do not need to know about the Redux store.
Test file:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import expect from 'expect';
import { shallow, render, mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
// import both the wrapped and unwrapped versions of the component
import ConnectedFeature, { feature as UnconnectedFeature } from '../index';
const mockStore = configureStore([thunk]);
describe('<Feature />', () => {
let store;
beforeEach(() => {
store = mockStore({
feature: {
message: 'This is the message'
}
});
});
it('renders a <Feature /> component and calls fetchMessage', () => {
const props = {
fetchMessage: sinon.spy()
};
const wrapper = mount(
<Provider store={store}>
<connectedFeature {...props} />
</Provider>
);
expect(wrapper.find('Feature').length).toEqual(1);
expect(props.fetchMessage.calledOnce).toEqual(true);
});
});