With react-router, how do you test for a connected component? - unit-testing

I'm using enzyme and code for my testing. I'm in the process of trying to test my routes produced by react-router-dom. I'm not have any issues as long as the route is an undecorated, 'non-connected' route. But the tests blows up if the route is a 'connected' redux route. First here is my Routes component.
Note that the <PrivateRoute /> is just a HOC that first checks if a user is authorized before rending the route. For the sake of what I'm trying to test, you can view them as any other <Route /> from react-router-dom.
const Routes = () => {
return (
<Switch>
<Route exact path='/' component={WelcomeScreen} />
<Route path='/auth' component={Authorize} />
<PrivateRoute path='/text_editor' component={TextEditor} />
<PrivateRoute path='/files' component={FileSearch} />
<Route component={SplashPage} />
</Switch>
)
}
Here is a test on one of my "undecorated" route. This test passes without any issues
it('should direct to the home page', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<Routes />
</MemoryRouter>
)
expect(wrapper.find('WelcomeScreen')).to.have.length(1);
})
However, when I run the same test on a connected route....
it('should direct to the authorized page', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/auth']}>
<Routes />
</MemoryRouter>
)
expect(wrapper.find('Connect(Authorized')).to.have.length(1);
})
The tests blows up with the error:
Invariant Violation: Could not find "store" in either the context or
props of "Connect(Authorize)". Either wrap the root component in a
, or explicitly pass "store" as a prop to
"Connect(Authorize)".
So I refactored the test to include a mockStore using redux-mock-store...
it('should direct to the authorize page', () => {
const mockStore = configureStore()
const store = mockStore({ state: '' })
const component = mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/auth']}>
<Routes />
</MemoryRouter>
</Provider>
)
expect(component.find('Connect(Authorize)')).to.have.length(1);
})
But now my error message is:
TypeError: Cannot convert object to primitive value
I'm now at a loss as to how to pass the store to the connected component to test its existence? Does any one have any ideas on the best way of testing a connected route?

Enzyme's mount does a full rendering. It is much slower than shallow and renders any children in addition to the component itself. A full render also requires, as you have found, that you provide mocks for everything the component might need.
Calling mount like this ends up being closer to an end-to-end test than a unit test.
Unit tests are really about testing what is specific to that component. In this case you only need to test that you have properly configured the Route elements. Any child components have their own tests, and Switch and Route should be tested in the library they are from.
Here is an example of how I unit tested a similar component:
// users.js
import * as React from 'react';
import { Route, Switch } from 'react-router-dom';
import EditUser from './editUser';
import UserList from './userList';
export default function Users() {
return (
<Switch>
<Route exact={true} path="/users" component={UserList}/>
<Route exact={true} path="/users/createuser" component={EditUser}/>
<Route path="/users/:id" component={EditUser}/>
</Switch>
);
}
// users.test.js
import { shallow } from 'enzyme';
import * as React from 'react';
import EditUser from './editUser';
import UserList from './userList';
import Users from './users';
describe("Users", () => {
describe('component', () => {
let element;
beforeEach(() => {
element = <Users />
});
it('renders as expected', () => {
const component = shallow(element);
expect(component).toMatchSnapshot();
});
it('routes /users to UserList', () => {
const component = shallow(element);
expect(component.find('Route[exact=true][path="/users"]').first().prop('component')).toBe(UserList);
});
it('routes /users/createuser to EditUser', () => {
const component = shallow(element);
expect(component.find('Route[exact=true][path="/users/createuser"]').first().prop('component')).toBe(EditUser);
});
it('routes /users/:id to EditUser', () => {
const component = shallow(element);
expect(component.find('Route[path="/users/:id"]').first().prop('component')).toBe(EditUser);
});
});
});
Unit testing like this targets just the component itself keeping your tests isolated, targeted, and fast. You can ensure that everything comes together and behaves properly as a whole in your end-to-end tests.

Related

How to test content of <Text /> tag in Jest + Enzyme + React Native?

I want to unit test with Jest and Enzyme if my <Text /> tag correctly receives props.header as text.
Usually I was able to test the content of the <Text /> tag like this:
it("should render a label", () => {
expect(wrapper.find(Text).contains("submit")).toBe(true);
});
But as soon as I pass an object this is no longer possible. Let me show you:
const createTestProps = props => ({
header: SOME_CONSTANT,
...props
});
...
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<MyList {...props} loaded={false} />);
});
it("should render a header", () => {
expect(wrapper.find(Text).contains(props.header)).toBe(true);
});
This fails with the following error message:
● MyList › rendering › still loading › should render a header
expect(received).toBe(expected) // Object.is equality
Expected: true
Received: false
How could I test this using Jest and Enzyme?
Edit
I found out that it has something to do passing a constant to the props. If I hardcode the value of props like this:
const createTestProps = props => ({
header: "Some hardcoded value",
...props
});
The test also passes. Is there a way to make this work even with a constant?

React Highcharts Jest testing error: `InvalidCharacterError`

I'm running into an issue while trying to do some basic smoke testing for React components that use react-highcharts. My typical method with basic Jest yields an error:
it('renders without crashing', () => {
const div = document.createElement('div');
render(<MyComponent {...props} />, div);
});
—>
InvalidCharacterError
at exports.name (node_modules/jest-environmentjsdom/node_modules/jsdom/lib/jsdom/living/helpers/validate-names.js:10:11)
at a.createElement (node_modules/highcharts/highcharts.js:17:221)
at Object.a.svg.z.init (node_modules/highcharts/highcharts.js:92:155)
at Object.z.createElement (node_modules/highcharts/highcharts.js:63:3)
at Object.a.svg.z.createElement (node_modules/highcharts/highcharts.js:107:525)
at Object.a.svg.z.init (node_modules/highcharts/highcharts.js:101:44)
at Object.a.svg.a.VMLRenderer.B (node_modules/highcharts/highcharts.js:109:320)
at Object.N.getContainer (node_modules/highcharts/highcharts.js:252:329)
From some interwebs sleuthing, it seems that this is an inherent problem with rendering <ReactHighcharts /> as a child component. How can I get around this without restructuring my component or complicating my testing?
Since the problem is rendering <ReactHighcharts /> as a child component, and we're just trying to make sure the parent component doesn't blow up, we can use Enzyme's shallow method to render only that parent component without the children:
it('renders without crashing', () => {
expect(shallow(<MyComponent {...props} />).exists()).toBeTruthy();
});

Testing a sub-component with a Link: '<Link>s rendered outside of a router context cannot navigate.'

I have a button component that creates a react-router Link element. It also allows an onClick function to be passed in for additional functionality (e.g. sending a Google Analytics event).
I have included this component in a parent, like so:
export default class Page extends Component {
const doSomething = () => {
//do a thing to test here
}
return (
<div>
<Button
onClickFn{() => doSomething()}
linkToUrl='/other/page' //this creates a <Link> inside the button
/>
</div>
)
}
Problem comes when I want to test that doSomething is being triggered correctly. I have used Enzyme mount to create the test Page component including the button. When I simulate a click I get the following error
'<Link>s rendered outside of a router context cannot navigate.'
because the Link in the button has no context. Is there a way of mocking this or preventing the error from showing? Or is there a better way of testing this functionality?
In your test, you will need to render the component within a <Router>. You can take a look at the tests for the <Link> component for examples on how to do this.
The basic idea is to create a memory history instance, pass that to the <Router>, and then render the <Link> inside of a <Route> passed to that. It sounds a bit involved, but it is fairly simple.
import { createMemoryHistory } from 'history'
it('clicks', () => {
const history = createMemoryHistory()
const App = () => (
<Router history={history}>
<Route path='/' component={Page} />
</Router>
)
})
Building on top of Paul's answer, here's a more detailed example for testing the onClick of a Button (or its Link child to be more precise). The example uses the testing libraries mocha (BDD test runner), chai (BDD assertions), enzyme (React testing utility), and sinon (test doubles).
import React from 'react';
import { Router, Route } from 'react-router';
import { createMemoryHistory } from 'history';
import MyCustomPage from '/.index';
describe('MyCustomPage', function(){
it('stores data when clicking the link', function() {
// Arrange
const Page = () => (
<MyCustomPage foo="foo" bar="bar" />
);
const container = enzyme.mount(
<Router history={createMemoryHistory()}>
<Route path="/" component={Page} />
</Router>
);
// Act
container.find('#my-link').simulate('click');
// Assert
expect(sessionStorage.setItem).to.have.been.calledWith('key', 'value');
});
});

Angular2 Testing Component with Provider dependency

I have a simple angular2 component as defined below. And I'm looking to create a unit testing with karma, jasmine to run through this component.
#Component({
selector: 'property',
template: require('./property.component.html'),
directives: [Panel],
providers: [ConfigService]});
export class PropertyComponent {
config:any;
constructor(config:ConfigService) {
this.config = config.getConfig();
}
}
This is my testing spec file.
describe('property component', () => {
it('should have property page title', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(PropertyComponent).then((fixture) => {
let propertyComp = fixture.componentInstance,
element = fixture.nativeElement;
expect(element.querySelector('h1').innerText).toBe('property page');
});
}));
})
However I got a list of weird errors... I'm guessing this is due to the ConfigService Provider in the PropertyComponent, because when I removed the provider dependency, it went through.
Does anyone know how to deal with the dependency Providers?
Thanks!
errors:
_instantiateProvider#angular2-seed/config/spec-bundle.js:23435:38
_new#angular2-seed/config/spec-bundle.js:23424:42
getObjByKeyId#angular2-seed/config/spec-bundle.js:22937:38
_getByKeyDefault#angular2-seed/config/spec-bundle.js:23641:51
_getByKey#angular2-seed/config/spec-bundle.js:23587:42
_getByDependency#angular2-seed/config/spec-bundle.js:23573:35
_instantiate#angular2-seed/config/spec-bundle.js:23463:53
_instantiateProvider#angular2-seed/config/spec-bundle.js:23435:38
_new#angular2-seed/config/spec-bundle.js:23424:42
instantiateProvider#angular2-seed/config/spec-bundle.js:22924:35
init#angular2-seed/config/spec-bundle.js:34694:44
AppElement#angular2-seed/config/spec-bundle.js:34371:33
viewFactory_HostPropertyComponent0
createRootHostView#angular2-seed/config/spec-bundle.js:35741:48
You need to use beforeEachProviders in this case:
import {beforeEachProviders, describe, it, expect} from 'angular2/testing';
//...other imports...
describe('property component', () => {
beforeEachProviders(()=> [
ConfigService, //if you don't need to mock
provide(ConfigService, {useClass:MockConfigService}) // more typical
]);
it('should have property page title', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(PropertyComponent).then((fixture) => {
//expectations...
});
}));
})
Note that you need to import angular's patched describe, it, expect functions along with beforeEachProvidersfrom angular2/testing. I emphasize this because it's easy to forget to do that, and it results in failures with rather unintuitive messages.

How to Unit Test React-Redux Connected Components?

I am using Mocha, Chai, Karma, Sinon, Webpack for Unit tests.
I followed this link to configure my testing environment for React-Redux Code.
How to implement testing + code coverage on React with Karma, Babel, and Webpack
I can successfully test my action and reducers javascript code, but when it comes to testing my components it always throw some error.
import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils'; //I like using the Test Utils, but you can just use the DOM API instead.
import chai from 'chai';
// import sinon from 'sinon';
import spies from 'chai-spies';
chai.use(spies);
let should = chai.should()
, expect = chai.expect;
import { PhoneVerification } from '../PhoneVerification';
let fakeStore = {
'isFetching': false,
'usernameSettings': {
'errors': {},
'username': 'sahil',
'isEditable': false
},
'emailSettings': {
'email': 'test#test.com',
'isEmailVerified': false,
'isEditable': false
},
'passwordSettings': {
'errors': {},
'password': 'showsomestarz',
'isEditable': false
},
'phoneSettings': {
'isEditable': false,
'errors': {},
'otp': null,
'isOTPSent': false,
'isOTPReSent': false,
'isShowMissedCallNumber': false,
'isShowMissedCallVerificationLink': false,
'missedCallNumber': null,
'timeLeftToVerify': null,
'_verifiedNumber': null,
'timers': [],
'phone': '',
'isPhoneVerified': false
}
}
function setup () {
console.log(PhoneVerification);
// PhoneVerification.componentDidMount = chai.spy();
let output = TestUtils.renderIntoDocument(<PhoneVerification {...fakeStore}/>);
return {
output
}
}
describe('PhoneVerificationComponent', () => {
it('should render properly', (done) => {
const { output } = setup();
expect(PhoneVerification.prototype.componentDidMount).to.have.been.called;
done();
})
});
This following error comes up with above code.
FAILED TESTS:
PhoneVerificationComponent
✖ should render properly
Chrome 48.0.2564 (Mac OS X 10.11.3)
Error: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
Tried switching from sinon spies to chai-spies.
How should I unit test my React-Redux Connected Components(Smart Components)?
A prettier way to do this, is to export both your plain component, and the component wrapped in connect. The named export would be the component, the default is the wrapped component:
export class Sample extends Component {
render() {
let { verification } = this.props;
return (
<h3>This is my awesome component.</h3>
);
}
}
const select = (state) => {
return {
verification: state.verification
}
}
export default connect(select)(Sample);
In this way you can import normally in your app, but when it comes to testing you can import your named export using import { Sample } from 'component'.
The problem with the accepted answer is that we are exporting something unnecessarily just to be able to test it. And exporting a class just to test it is not a good idea in my opinion.
Here is a neater solution without the need of exporting anything but the connected component:
If you are using jest, you can mock connect method to return three things:
mapStateToProps
mapDispatchToProps
ReactComponent
Doing so is pretty simple. There are 2 ways: Inline mocks or global mocks.
1. Using inline mock
Add the following snippet before the test's describe function.
jest.mock('react-redux', () => {
return {
connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({
mapStateToProps,
mapDispatchToProps,
ReactComponent
}),
Provider: ({ children }) => children
}
})
2. Using file mock
Create a file __mocks__/react-redux.js in the root (where package.json is located)
Add the following snippet in the file.
module.exports = {
connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({
mapStateToProps,
mapDispatchToProps,
ReactComponent,
}),
Provider: ({children}) => children
};
After mocking, you would be able to access all the above three using Container.mapStateToProps,Container.mapDispatchToProps and Container.ReactComponent.
Container can be imported by simply doing
import Container from '<path>/<fileName>.container.js'
Hope it helps.
Note that if you use file mock. The mocked file will be used globally for all the test cases(unless you do jest.unmock('react-redux')) before the test case.
Edit: I have written a detailed blog explaining the above in detail:
http://rahulgaba.com/front-end/2018/10/19/unit-testing-redux-containers-the-better-way-using-jest.html
You can test your connected component and I think you should do so. You may want to test the unconnected component first, but I suggest that you will not have complete test coverage without also testing the connected component.
Below is an untested extract of what I do with Redux and Enzyme. The central idea is to use Provider to connect the state in test to the connected component in test.
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import SongForm from '../SongForm'; // import the CONNECTED component
// Use the same middlewares you use with Redux's applyMiddleware
const mockStore = configureMockStore([ /* middlewares */ ]);
// Setup the entire state, not just the part Redux passes to the connected component.
const mockStoreInitialized = mockStore({
songs: {
songsList: {
songs: {
songTags: { /* ... */ }
}
}
}
});
const nullFcn1 = () => null;
const nullFcn2 = () => null;
const nullFcn3 = () => null;
const wrapper = mount( // enzyme
<Provider store={store}>
<SongForm
screen="add"
disabled={false}
handleFormSubmit={nullFcn1}
handleModifySong={nullFcn2}
handleDeleteSong={nullFcn3}
/>
</Provider>
);
const formPropsFromReduxForm = wrapper.find(SongForm).props(); // enzyme
expect(
formPropsFromReduxForm
).to.be.deep.equal({
screen: 'add',
songTags: initialSongTags,
disabled: false,
handleFormSubmit: nullFcn1,
handleModifySong: nullFcn2,
handleDeleteSong: nullFcn3,
});
===== ../SongForm.js
import React from 'react';
import { connect } from 'react-redux';
const SongForm = (/* object */ props) /* ReactNode */ => {
/* ... */
return (
<form onSubmit={handleSubmit(handleFormSubmit)}>
....
</form>
};
const mapStateToProps = (/* object */ state) /* object */ => ({
songTags: state.songs.songTags
});
const mapDispatchToProps = () /* object..function */ => ({ /* ... */ });
export default connect(mapStateToProps, mapDispatchToProps)(SongForm)
You may want to create a store with pure Redux. redux-mock-store is just a light-weight version of it meant for testing.
You may want to use react-addons-test-utils instead of airbnb's Enzyme.
I use airbnb's chai-enzyme to have React-aware expect options. It was not needed in this example.
redux-mock-store is an awesome tool to test redux connected components in react
const containerElement = shallow((<Provider store={store}><ContainerElement /></Provider>));
Create fake store and mount the component
You may refer to this article Testing redux store connected React Components using Jest and Enzyme | TDD | REACT | REACT NATIVE
Try creating 2 files, one with component itself, being not aware of any store or anything (PhoneVerification-component.js). Then second one (PhoneVerification.js), which you will use in your application and which only returns the first component subscribed to store via connect function, something like
import PhoneVerificationComponent from './PhoneVerification-component.js'
import {connect} from 'react-redux'
...
export default connect(mapStateToProps, mapDispatchToProps)(PhoneVerificationComponent)
Then you can test your "dumb" component by requiring PhoneVerification-component.js in your test and providing it with necessary mocked props. There is no point of testing already tested (connect decorator, mapStateToProps, mapDispatchToProps etc...)