import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as CommonActions from 'common/actions/common.actions';
import Activity from './activity.component';
const mapStateToProps = ({ common }) => ({common});
const mapDispatchToProps = dispatch => bindActionCreators(
{
getNationalities: CommonActions.getNationalities,
},dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Activity);
How Can I test a container component like this which contains mapStateToProps, mapDispatchToProps and connect ??
React-test-render can test component, You have to import component that would include your mapStateToProps, mapDispatchToProps.
Example:
<YourComponentName
common={jest.fn()}
getNationalities={jest.fn()}
/>
Related
I am writing unit tests for VueJS components and have consulted the "Applying Global Plugins and Mixins" section of Vue Test Utils Common Tips. I have a component that depends on the Vuex store so it makes sense that I would transpose the example under that section for my purposes.
Here is my code for that component's specific .spec.js file:
import { createLocalVue, mount } from '#vue/test-utils'
import AppFooter from '#/components/AppFooter/AppFooter'
import store from '#/store'
describe('AppFooter component', () => {
const localVue = createLocalVue()
localVue.use(store)
it('AppFooter should have header slot', () => {
const AppFooterComponent = mount(AppFooter, {
localVue
})
/* TODO: Replace with a more appropriate assertion */
expect(true).toEqual(true)
})
})
This is pretty faithful to the example provided in the link above. However, the error I receive when I run the test suite is as follows:
Should I be installing the Vue store differently?
To elaborate on my comment, I believe it should look like the following, where you pass in the store on the mount() call.
import { createLocalVue, mount } from '#vue/test-utils'
import AppFooter from '#/components/AppFooter/AppFooter'
import Vuex from 'vuex'
import store from '#/store' //you could also mock this out.
describe('AppFooter component', () => {
const localVue = createLocalVue()
localVue.use(Vuex)
it('AppFooter should have header slot', () => {
const AppFooterComponent = mount(AppFooter, {
store,
localVue
})
/* TODO: Replace with a more appropriate assertion */
expect(true).toEqual(true)
})
})
I believe that you have something like this.$store.getters[someBeautifulGetterName] in you component. To make your tests mount the component your need to initialise store and pass it into your testing component. Just keep in mind that this would be a brand new instance of Vuex. Here is the code
import { shallowMount } from '#vue/test-utils'
import Vue from 'vue'
import Vuex from 'vuex'
import Tags from '#/components/Tags'
Vue.use(Vuex)
Vue.prototype.$store = new Vuex.Store()
const factory = (propsData) => {
return shallowMount(Tags, {
propsData: {
...propsData
}
})
}
describe('Tags', () => {
it("render tags with passed data", () => {
const wrapper = factory({ loading: true })
// TODO:
})
})
I'm trying to to do integration tests, by mounting a smart connected component.
The fetch action that is within componentDidMount of my smart components dispatches just fine, and it's taken by my Saga. Although it is supposed to put a success action, it doesn't .
Here is my testing code :
import React from 'react'
import { Provider } from 'react-redux'
import configureMockStore from 'redux-mock-store'
import MockAdapter from 'axios-mock-adapter'
import Enzyme,{ mount,render } from 'enzyme'
import Tasks from '../containers/tasks.jsx'
import createSagaMiddleware from 'redux-saga'
import axios from "axios";
import Adapter from 'enzyme-adapter-react-16';
import mySaga from '../actions/tasksSaga'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import reducer from '../reducers'
Enzyme.configure({ adapter: new Adapter() });
describe('App', () => {
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
const mock = new MockAdapter(axios)
const state = {
tasksReducer:{
tasks:[]
},
uiReducer :{
}
};
const todos = [
{
id: 1,
title: 'todo1',
description: 'nice'
},
{
id: 2,
title: 'todo2',
description: 'nice'
}
]
beforeAll(() => {
mock.onGet('http://localhost:9001/tasks').reply(200, {tasks:todos})
})
it('renders an App container', () => {
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware),
)
sagaMiddleware.run(mySaga)
const wrapper = mount(
<Provider store={store}>
<Tasks />
</Provider>
)
wrapper.instance().forceUpdate()
expect(wrapper.find('Task')).toHaveLength(3)
})
})
My success action is never called although data is good.
I'm building up an AppHeader component that connects to a Redux store to get its props. Ideally, I'd probably do it different, but decided to use as an exercise in testing a connected component (I'm new to React).
I'm trying to test this component by using redux-mock-store, but when I get it working in tests, the UI fails. When I get it working in the UI, the tests fail.
The PROBLEM_LINE in AppHeader.component.js below, is where the symptom originates.
When set to appHeader: state.get('AppHeader'), the tests pass successfully, but the console shows:
Uncaught TypeError: state.get is not a function at
Function.mapStateToProps [as mapToProps]`
When set to appHeader: state.AppHeader, the UI correctly displays "Real App Title" inside of an but the test now throws:
TypeError: Cannot read property 'toJS' of undefined
This seems to be an issue with using Immutable.js structures. If I change both initialState variables using plain JS objects, the tests pass and the correct value is displayed. I feel like I must be using immutable incorrectly, or am not getting stores set up correctly with it.
I've read nearly all the posts returned by Google re: testing connected components/containers but most were using state.AppHeader or state.get('AppHeader') in mapStateToProps, and most except a precious few were more on how to hook up redux and react but not so much testing it.
I tried to forego using redux-mock-store, and creating my own store (i.e. a function with dispatch, subscribe, etc) but that didn't solve any problems and only created new ones. Maybe I need to reinvestigate that if that's a better way in the end.
src/index.js
import React from 'react';
import {render} from 'react-dom';
import {Provider} from 'react-redux'
import {Router, browserHistory} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux';
import configureStore from './store/configureStore';
import routes from './routes';
const store = configureStore;
const history = syncHistoryWithStore(browserHistory, store);
render(
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>,
document.getElementById('root')
);
src/store/configureStore.js
import {createStore, combineReducers} from 'redux';
import {routerReducer} from 'react-router-redux';
import * as reducers from '../ducks';
const rootReducer = combineReducers({
routing: routerReducer,
...reducers
});
export default createStore(
rootReducer
);
src/ducks/index.js
import AppHeader from './AppHeader.duck';
export {
AppHeader
};
src/ducks/AppHeader.duck.js
import {fromJS} from 'immutable';
////////////
/// Actions
const SET_APP_TITLE = 'new-react/AppHeader/SET_APP_TITLE';
////////////
/// Reducer
const initialState = fromJS({ <-- USING IMMUTABLE HERE
title: 'Real App Title'
});
export default function reducer(state = initialState, action){
switch(action.type){
case SET_APP_TITLE:
return state.set('title', action.payload.title);
default:
return state;
}
}
////////////
/// Action Creators
export function setAppTitle(title){
return {
type: SET_APP_TITLE,
payload: {title}
};
}
module.exports = reducer;
src/components/AppHeader.component.js
import React from 'react';
import {connect} from 'react-redux';
export class AppHeader extends React.PureComponent {
render() {
const appHeader = this.props.appHeader;
return (
<div className="appHeader">
<h1 className="appHeader_title">
{appHeader.title}
</h1>
</div>
);
}
}
AppHeader.propTypes = {
appHeader: React.PropTypes.object
};
const mapStateToProps = state => {
return {
appHeader: state.AppHeader.toJS() <-- PROBLEM LINE
};
}
export default connect(
mapStateToProps
)(AppHeader);
src/components/AppHeader.component.spec.js
import React from 'react';
import ReactDOM from 'react-dom';
import {mount} from 'enzyme';
import {Provider} from 'react-redux';
import configureMockStore from 'redux-mock-store'
import {fromJS} from 'immutable';
import AppHeader from './AppHeader';
let initialState = fromJS({ <-- USING IMMUTABLE AGAIN
AppHeader: {
title: 'Mock App Title'
}
});
const mockStore = configureMockStore([])(initialState);
describe('AppHeader', () => {
let component;
beforeEach(() => {
const wrapper = mount(
<Provider store={mockStore}>
<AppHeader />
</Provider>
);
component = wrapper.find('AppHeader');
});
it('renders without crashing', () => {
expect(component).toBeDefined();
});
it('shows the app title', () => {
expect(component.find('.appHeader_title').text())
.toBe('App Header Title');
});
});
All dependencies were installed in the last day or so, so are the latest version from npm install. The app itself was created with create-react-app.
I am using Enzyme to unit test my React components. I understand that in order to test the raw unconnected component I'd have to just export it and test it (I've done that). I have managed to write a test for the connected component but I am really not sure if this's the right way and also what exactly would I want to test for the connected component.
Container.jsx
import {connect} from 'react-redux';
import Login from './Login.jsx';
import * as loginActions from './login.actions';
const mapStateToProps = state => ({
auth: state.auth
});
const mapDispatchToProps = dispatch => ({
loginUser: credentials => dispatch(loginActions.loginUser(credentials))
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Container.test.js
import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
describe('Container Login', () => {
it('should render the container component', () => {
const storeFake = state => ({
default: () => {
},
subscribe: () => {
},
dispatch: () => {
},
getState: () => ({ ...state })
});
const store = storeFake({
auth: {
sport: 'BASKETBALL'
}
});
const wrapper = mount(
<Provider store={store}>
<LoginContainer />
</Provider>
);
expect(wrapper.find(LoginContainer).length).to.equal(1);
const container = wrapper.find(LoginContainer);
expect(container.find(Login).length).to.equal(1);
expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
});
});
This is an interesting question.
I usually do import both container and component to do the testing. For container testing I use, redux-mock-store. Component testing is for testing async functions. For instance in your case, login process is an async function using sinon stubs. Here is a snippet of the same,
import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { stub } from 'sinon';
const mockStore = configureMockStore([thunk]);
describe('Container Login', () => {
let store;
beforeEach(() => {
store = mockStore({
auth: {
sport: 'BASKETBALL',
},
});
});
it('should render the container component', () => {
const wrapper = mount(
<Provider store={store}>
<LoginContainer />
</Provider>
);
expect(wrapper.find(LoginContainer).length).to.equal(1);
const container = wrapper.find(LoginContainer);
expect(container.find(Login).length).to.equal(1);
expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
});
it('should perform login', () => {
const loginStub = stub().withArgs({
username: 'abcd',
password: '1234',
});
const wrapper = mount(<Login
loginUser={loginStub}
/>);
wrapper.find('button').simulate('click');
expect(loginStub.callCount).to.equal(1);
});
});
As you pointed out, the way I usually do this is to export the un-connected component as well, and test that.
i.e.
export {Login};
Here's an example. Source of the component, and source of the tests.
For the wrapped component, I don't author tests for those because my mappings (mapStateToProps and mapDispatchToProps) are generally very simple. If I wanted to test a wrapped component, I'd really just be testing those maps. So those are what I would choose to explicitly test, rather than re-testing the entire component in a wrapped form.
There are two ways to test those functions. One way would be to export the functions within the module itself.
i.e.;
export {mapStateToProps, mapDispatchToProps}
I'm not a huge fan of this, because I wouldn't want other modules in the app to access them. In my tests, I sometimes use babel-plugin-rewire to access "in-scope" variables, so that's what I would do in this situation.
That might look something like:
import {
Login, __Rewire__
}
const mapStateToProps = __Rewire__.__get__('mapStateToProps');
describe('mapStateToProps', () => { ... });
If we have a router issue, we can consider to add the router lib into the test file, eg:
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { mount } from 'enzyme';
import ReadDots from './ReadDots';
const storeFake = state => ({
default: () => {
},
subscribe: () => {
},
dispatch: () => {
},
getState: () => ({ ...state })
});
const store = storeFake({
dot: {
dots: [
{
id: '1',
dot: 'test data',
cost: '100',
tag: 'pocket money'
}
]
}
});
describe('<ReadDots />', () => {
it('should render ReadDots component', () => {
const component = mount(
<Provider store={store}>
<Router>
<ReadDots />
</Router>
</Provider>
);
expect(component.length).toEqual(1);
});
});
I have the following components:
// Hello.js
export default (React) => ({name}) => {
return (
<div>
Hello {name ? name : 'Stranger'}!
</div>
)
}
// App.js
import createHello from './Hello'
export default (React) => () => {
const Hello = createHello(React)
const helloProps = {
name: 'Jane'
}
return (
<Hello { ...helloProps } />
)
}
// index.js
import React from 'react'
import { render } from 'react-dom'
import createApp from './App'
const App = createApp(React)
render(
<App />,
document.getElementById('app')
)
And I want to set up a test to see if the App component contains one Hello component. I tried the following, using Tape and Enzyme:
import createApp from './App'
import React from 'react'
import test from 'tape'
import { shallow } from 'enzyme'
test('App component test', (assert) => {
const App = createApp(React)
const wrapper = shallow(<App />)
assert.equal(wrapper.find('Hello').length === 1, true)
})
But the result was that the length property of the find result was equal to 0, when I was expecting it to be equal to 1. So, how do I find my Hello component?
There are a couple of things you can do in this case. Enzyme can match component constructors based on the constructor's static .displayName or .name properties, or by referential equality. As a result, the following approaches should all work:
Direct Reference
you can import the actual components in your tests and find them using direct references to the component:
// NavBar-test.js
import NavBar from './path/to/NavBar';
...
wrapper.find(NavBar).length)
Named Function Expressions
If you use named function expressions to create your stateless functional components, the names should still work.
// NavBar.js
module.exports = function NavBar(props) { ... }
Static .displayName property
You can add a static .displayName property on the components:
// NavBar.js
const NavBar = (props) => { ... };
NavBar.displayName = 'NavBar';
Try to import the Hello component in the top of your file and then update your assertion to find the actual component and not the name of it. Like below:
import createApp from './App'
import Hello from './Hello'
import React from 'react'
import test from 'tape'
import { shallow } from 'enzyme'
test('App component test', (assert) => {
const App = createApp(React)
const wrapper = shallow(<App />)
assert.equal(wrapper.find(Hello).length === 1, true)
})
Btw for all the enzyme users out there the assertion would be something like:
expect(wrapper.find(Hello)).toHaveLength(1);