I have a React component that has buttons for which the onClick events are bound to functions passed as properties from the parent component, e.g. <Counter counter=0 incrementCounter={incrementFunction} decrementCounter={decrementFunction} />.
I'm new to unit testing React components so I'm trying to check that the function gets called when clicking on the increment/decrement buttons. For this I'm using Jasmine's spyOn method, but it never seems to catch the function being called.
If I output a console.log in the function I'm using (e.g. let incrementCounter = () => { console.log("increment!"); };) then I can tell the function is being called when I do TestUtils.Simulate.click(incrementButton);, however the test still won't pass. What am I missing?
Counter.js
import React, { Component, PropTypes } from "react";
class Counter extends Component {
constructor(props) {
super(props);
}
render() {
const { incrementCounter, decrementCounter, counter } = this.props;
return (
<div>
<h1>Counter</h1>
<p>
<b>Counter: {counter} times</b>
{" "}
<button onClick={incrementCounter}>+</button>
{" "}
<button onClick={decrementCounter}>-</button>
</p>
</div>
);
}
}
Counter.propTypes = {
incrementCounter: PropTypes.func.isRequired,
decrementCounter: PropTypes.func.isRequired,
counter: PropTypes.number.isRequired
};
export default Counter;
Counter.test.js
import React from "react";
import TestUtils from "react/lib/ReactTestUtils";
import Counter from "./Counter"
describe("Counter", function () {
let renderedComponent = {};
let heading = {};
let decrementButton = {};
let incrementButton = {};
let incrementCounter = () => {};
let decrementCounter = () => {};
let counter = 0;
beforeEach(function(){
renderedComponent = TestUtils.renderIntoDocument(
<Counter incrementCounter={incrementCounter} decrementCounter={decrementCounter} counter={counter} />
);
heading = TestUtils.findRenderedDOMComponentWithTag(renderedComponent, "h1");
let buttons = TestUtils.scryRenderedDOMComponentsWithTag(renderedComponent, "button");
decrementButton = buttons[1];
incrementButton = buttons[0];
this.incrementCounter = incrementCounter;
});
it("renders without problems", function () {
expect(TestUtils.isDOMComponent(heading)).toBe(true);
expect(heading.innerText).toMatch(/Counter/g);
expect(TestUtils.isDOMComponent(decrementButton)).toBe(true);
expect(decrementButton.innerText).toMatch(/-/g);
expect(TestUtils.isDOMComponent(incrementButton)).toBe(true);
expect(incrementButton.innerText).toMatch(/\+/g);
});
it("fires the increment function", function() {
spyOn(this, "incrementCounter");
TestUtils.Simulate.click(incrementButton);
expect(this.incrementCounter).toHaveBeenCalled(); // Error: fuction doesn't get called
});
});
I'm not quite sure how Jasmine actually wraps functions with spyOn, but try setting this.incrementCounter = incrementCounter at the top of beforeEach and use that directly in the component:
beforeEach(function(){
let that = this;
this.incrementCounter = incrementCounter;
renderedComponent = TestUtils.renderIntoDocument(
<Counter incrementCounter={that.incrementCounter} decrementCounter={decrementCounter} counter={counter} />
);
If that still doesn't work, because of a glitch with that/this, just declare incrementCounter as a spy from the beginning:
let incrementCounter = jasmine.createSpy('incrementCounter')
and use that in <Counter /> and the rest of the test.
Related
Hy, I don't know how to mock an inline function in React component's child
My stack: sinon, chai, enzyme;
Component usage:
<ListItem onClick={() => someFn()} />
Component's render:
render() {
return (
<li>
<a href="#" onClick={e => {
e.preventDefault();
this.props.onClick();
}}
> whatever </a>
</li>
);
}
Here we have onClick function that calls e.preventDefault(). How to tell to <a href>(link) to not to call e.preventDefault()? How can I mock an onClick?
Below is what I have tried in tests:
Shallow copy setup
function setup() {
const someFn = sinon.stub();
const component = shallow(
<ListItem
onClick={() => {
someFn();
}}
/>
);
return {
component: component,
actions: someFn,
link: component.find('a'),
listItem: component.find('li'),
}
}
And the test
it('simulates click events', () => {
const { link, actions } = setup();
link.simulate('click'); //Click on <a href>
expect(actions).to.have.property('callCount', 1); //will be fine if we remove e.preventDefault()
});
Test's output error:
TypeError: Cannot read property 'preventDefault' of undefined
Try this
link.simulate('click', {
preventDefault: () => {
}
});
test('simulates click events', () => {
const e = { stopPropagation: jest.fn() };
const component = shallow(<ListItem{...props} />);
const li = component.find('li').at(0).childAt(0)
li.props().onClick(e)
expect();
});
For those using Jest and #testing-library or react-testing-librarys fireEvent, you need to provide an initialised event object, otherwise the event can't be dispatched via your element.
One can then assert on e.preventDefault being called by assigning a property to that initialised event:
test('prevents default on click', () => {
const {getByText} = render(<MyComponent />);
const button = getByText(/click me/);
// initialise an event, and assign your own preventDefault
const clickEvent = new MouseEvent('click');
Object.assign(clickEvent, {preventDefault: jest.fn()});
fireEvent(button, clickEvent);
expect(clickEvent.preventDefault).toHaveBeenCalledTimes(1);
});
Similarly for stopPropagation.
Anton Karpenko's answer for Jest was useful.
Just to note that this is an issue only when using shallow enzyme renderer. In case of full DOM renderer mount, the event object contains the preventDefault method, therefore you don't have to mock it.
You can define an object with regarding function you will mock via some testing tool, for example look at Jest and Enzyme
describe('Form component', () => {
test('deos not reload page after submition', () => {
const wrapper = shallow(<TodosForm />)
// an object with some function
const event = { preventDefault: () => {} }
// mocks for this function
jest.spyOn(event, 'preventDefault')
wrapper.find('form').simulate('submit', event)
// how would you know that function is called
expect(event.preventDefault).toBeCalled()
})
})
I would suggest to create new object based on jest.fn() with
const event = Object.assign(jest.fn(), {preventDefault: () => {}})
then use it:
element.simulate('click', event);
I am using Web Components and this works for me -
const callback = jest.fn();
MouseEvent.prototype.stopPropagation = callback;
const element = createElement({});
element.shadowRoot.querySelector('ul').click();
expect(callback).toHaveBeenCalledTimes(1);
I've got a wizard form made with redux-forms v6 and it looks something like:
--
index.js - Holds page number in local state, is connected to application level state
PageOne - wrapped with reduxForm decorator (form: 'wizForm')
PageTwo - wrapped with reduxForm decorator (form: 'wizForm')
--
PageOne and PageTwo both contain additional components that render sections of the form (initial fields, vehicle information, driver information...), and each of those sections render their own components for each question in that section.
Since there's a lot of nested components and I want to test that PageOne and PageTwo call the props passed from index.js, I've resorted to using Enzyme's mount() function with a fake store. I want to MatchSnapshot() with Jest to compare whether index.js is rendering PageOne or PageTwo, after certain buttons are clicked to go back and forth from pages.
The problem is when I do create snapshots, other than creating a 16,000 line snapshot, the snapshot will NEVER match the previous one even if I don't change anything. I'm not sure if it's redux-form that's doing it or React, but the htmlFor and the id keep changing between snapshots, test after test after test.
We use css-modules too, but I don't think that's causing the problem, and we did configure Jest to work with css-modules too, modifying "moduleNameWrapper" to mock .css files. Does anyone know how to fix this or where I should look?
tests:
describe('<VehicleAddition />', () => {
let props;
beforeEach(() => {
props = {
...,
};
});
it('Renders initially', () => {
const component = shallow(<VehicleAddition {...props} />);
expect(toJson(component)).toMatchSnapshot();
});
it('Renders <PageTwo> when <PageOne> form is submitted', () => {
const component = shallow(<VehicleAddition {...props} />);
expect(toJson(component)).toMatchSnapshot();
component.find('ReduxForm') // reduxForm HOC wraps the <form> in a <ReduxForm> component
.first()
.simulate('submit');
expect(toJson(component)).toMatchSnapshot();
expect(component.state().page).toEqual(2);
});
it('PageTwoStuffs', () => {
// Render the form, click 'next', assert it's page two
// click 'previous'
jest.enableAutomock();
const store = createStore(
combineReducers({
route: jest.fn(() => Immutable.fromJS({})),
language: jest.fn(() => Immutable.fromJS({})),
global: jest.fn(() => Immutable.fromJS({})),
form: formReducer,
}),
Immutable.fromJS({}),
);
const component = mount(
<Provider store={store}>
<VehicleAddition {...props} />
</Provider>
);
// CAN'T check the state of <VehicleAddition /> because it can only be done on root component, says the error message.
expect(toJson(component)).toMatchSnapshot();
index.js:
export class VehicleAddition extends React.Component { // eslint-disable-line
constructor(props) {
super(props);
this.state = {
page: 1,
};
}
nextPage = () => {
this.setState({ page: this.state.page + 1 });
}
previousPage = () => {
this.setState({ page: this.state.page - 1 });
}
render() {
return (
<div>
{page === 1 &&
<PageOne
{...this.props}
/>
}
{page === 2 &&
<PageTwo
{...this.props}
/>
}
</div>
);
}
}
PageOne.js
class PageOne extends React.Component { // eslint-disable-line
render() {
const {
...
} = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<InitialFields
autoPolicies={autoPolicies}
changeField={this.changeField}
getFormValues={getFormValues}
policies={policies}
primary={primary}
/>
<VehicleBeingAddedFields
changeField={this.changeField}
getFormValues={getFormValues}
fetchVehMakes={fetchVehMakes}
fetchVehModels={fetchVehModels}
policies={policies}
vehMakes={vehMakes}
vehModels={vehModels}
/>
...
<div className="btn-group btn-group-float-right">
<button
type="submit"
onClick={this.handleClick}
disabled={pristine || submitting}
className="btn-primary"
>
Next
</button>
</div>
</form>
);
}
}
PageTwo.js:
class PageTwo extends React.Component { // eslint-disable-line
render() {
const {
...
} = this.props;
return (
<form onSubmit={handleSubmit}>
...
<div className="btn-group btn-group-float-right">
<button type="button" className="btn" onClick={previousPage}>Previous</button>{' '}
<button type="submit" disabled={pristine || submitting} className="btn-primary">Submit</button>
</div>
</form>
);
}
}
Example of the parts of the snapshot that constantly changes:
I solved it by passing a hardcoded id value from the test cases
import React from 'react';
import renderer from 'react-test-renderer';
import { reduxForm } from 'redux-form';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { mount } from 'enzyme'
import TodoItem from './TodoItem';
import injectTapEventPlugin from 'react-tap-event-plugin';
function setup() {
const spy = jest.fn();
const store = createStore(() => ({}));
const Decorated = reduxForm({ form: 'testForm' })(TodoItem);
const props = {
remove: jest.fn(),
TodoItemReduxFormInitialName: "fullName",
snapshotTestId:"4"
}
const mockedComponent = <Provider store={store}>
<Decorated {...props} />
</Provider>;
const enzymeWrapper = mount(mockedComponent)
injectTapEventPlugin();
return {
props,
mockedComponent,
enzymeWrapper
}
}
describe('TodoItem Component', () => {
it('should render the snapshot', () => {
const {mockedComponent} = setup()
const tree = renderer.create(
mockedComponent
).toJSON();
expect(tree).toMatchSnapshot();
});
//not required as snapshot testing covers it
it('should render Number', () => {
const {enzymeWrapper} = setup()
const fieldProps = enzymeWrapper.find('Field').at(0).props();
expect(fieldProps.hintText).toEqual('Item Number');
expect(fieldProps.name).toEqual('fullName.itemNumber');
});
//not required as snapshot testing covers it
it('should render remove button', () => {
const {enzymeWrapper} = setup()
const button = enzymeWrapper.find('RaisedButton').at(0).props();
expect(button.label).toEqual("remove")
});
});
Can anyone explain how jest.fn() actually works, with a real world example, as I'm confused on how to use it and where it has to be used.
For example if I have the component Countries which fetches country List on click of a button with help of the Utils Function
export default class Countries extends React.Component {
constructor(props) {
super(props)
this.state = {
countryList:''
}
}
getList() {
//e.preventDefault();
//do an api call here
let list = getCountryList();
list.then((response)=>{ this.setState({ countryList:response }) });
}
render() {
var cListing = "Click button to load Countries List";
if(this.state.countryList) {
let cList = JSON.parse(this.state.countryList);
cListing = cList.RestResponse.result.map((item)=> { return(<li key={item.alpha3_code}> {item.name} </li>); });
}
return (
<div>
<button onClick={()=>this.getList()} className="buttonStyle"> Show Countries List </button>
<ul>
{cListing}
</ul>
</div>
);
}
}
Utils function used
const http = require('http');
export function getCountryList() {
return new Promise(resolve => {
let url = "/country/get/all";
http.get({host:'services.groupkt.com',path: url,withCredentials:false}, response => {
let data = '';
response.on('data', _data => data += _data);
response.on('end', () => resolve(data));
});
});
}
Where could I use jest.fn() or how can I test that getList() function is called when I click on the button?
Jest Mock Functions
Mock functions are also known as "spies", because they let you spy on the behavior of a function that is called indirectly by some other code, rather than just testing the output. You can create a mock function with jest.fn().
Check the documentation for jest.fn()
Returns a new, unused mock function. Optionally takes a mock implementation.
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
With a mock implementation:
const returnsTrue = jest.fn(() => true);
console.log(returnsTrue()) // true;
So you can mock getList using jest.fn() as follows:
jest.dontMock('./Countries.jsx');
const React = require('react/addons');
const TestUtils = React.addons.TestUtils;
const Countries = require('./Countries.jsx');
describe('Component', function() {
it('must call getList on button click', function() {
var renderedNode = TestUtils.renderIntoDocument(<Countries />);
renderedNode.prototype.getList = jest.fn()
var button = TestUtils.findRenderedDOMComponentWithTag(renderedNode, 'button');
TestUtils.Simulate.click(button);
expect(renderedNode.prototype.getList).toBeCalled();
});
});
I am fairly new with enzyme. I have two components under test.
form.jsx
const LoginForm = ({ style, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<Button type='submit'>
Login
</Button>
</form>
);
};
LoginForm.propTypes = {
handleSubmit: PropTypes.func.isRequired
};
I am using this component in another component as follows:
Component.jsx
export default class Login extends Component {
constructor(props) {
super(props);
this.onLogin = this.onLogin.bind(this);
}
onLogin(event) {
event.preventDefault();
this.props.loginUser();
}
render() {
return (
<LoginForm style={loginFormStyles} handleSubmit={this.onLogin} />
);
}
}
Login.propTypes = {
auth: PropTypes.object.isRequired, //mapStateToProps
loginUser: PropTypes.func.isRequired //mapDispatchToProps
};
I have written tests for form and they are passing.
form-test.js
it('should have a onSubmit handler', () => {
const props = {
handleSubmit: () => {}
};
const wrapper = shallow(<LoginForm {...props} />);
expect(_.isFunction(wrapper.props().onSubmit)).to.be.true;
});
it('should should call handlesubmit on form submission', () => {
const handleSubmit = sinon.spy();
const wrapper = shallow(<LoginForm handleSubmit={handleSubmit} />);
wrapper.simulate('submit');
expect(handleSubmit).to.have.been.called;
});
These tests are passing. The confusing part is:
1- How do I test onLogin function in Component.jsx from form.jsx?
2- Vice versa, if I have to trigger onSubmit of form.jsx from component.jsx how would I do that?
First of all, you can rename the Component.jsx to something else.
And for the test you can do something as below,
import Component from '../src/login';
import { stub } from 'sinon';
describe('login', () => {
it('should call onsubmit', () => {
const onSubmit = stub()
.withArgs('username', 'password');
const loginComponent = mount(<LoginForm handleSubmit={onSubmit} /> );
loginComponent.simulate('submit');
expect(onSubmit.calledOnce).to.equal(true);
});
});
I have not tested this but it is close to what you are looking at.
Update:
I tested this and it is working.
My app is a Fluxible / React application.
I have the following spec that attempts to test a LoginForm. Embedded components have been stubbed using rewire. I referenced http://fluxible.io/api/components.html#testing.
The first spec it("renders") passes. However, when I try to do more tests as shown in the commented code, the test fails.
I am unable to assert on LoginForm's state or trigger simulated events using TestUtils on the component. Are there any ways to do that?
import React from 'react/addons';;
import { createMockComponentContext } from 'fluxible/utils';
import createStore from 'fluxible/addons/createStore';
var rewire = require("rewire");
var rewireModule = require("../../helpers/rewire-module");
// stub inner components with LoginForm
// `rewire` instead of `require`
var LoginForm = rewire("../../../src/components/auth/login-form");
// Replace the required module with a stub component.
rewireModule(LoginForm, {
FormattedMessage: React.createClass({
render: function() { return <div />; }
}),
NavLink: React.createClass({
render: function() { return <div />; }
})
});
describe('LoginForm', function() {
var context;
var TestUtils;
var provideContext;
var connectToStores;
var MockIntlStore;
var MockAuthStore;
var noop = function(){};
var component;
beforeEach(function(){
MockIntlStore = createStore({
storeName: 'IntlStore',
getMessage: noop,
getState: function(){
return {}
}
});
MockAuthStore = createStore({
storeName: 'AuthStore'
});
context = createMockComponentContext({
stores: [MockIntlStore, MockAuthStore]
});
// React must be required after window is set
TestUtils = React.addons.TestUtils
provideContext = require('fluxible/addons/provideContext');
connectToStores = require('fluxible/addons/connectToStores');
// Wrap with context provider and store connector
LoginForm = provideContext(connectToStores(LoginForm, [MockIntlStore, MockAuthStore], function (stores) {
return {
};
}));
component = TestUtils.renderIntoDocument(
<LoginForm context={context} />
);
});
it("renders", function() {
var foundComponent = TestUtils.findRenderedDOMComponentWithClass(
component, 'login-form');
expect(foundComponent).toBeDefined();
});
// TODO fluxible wraps components so we cant reach the inner component to assert on state and trigger event handlers
// it("should have an initial state", function() {
// let initialState = {
// username: '',
// pass: ''
// }
// expect(component.state).toEqual(initialState);
// });
});
When you use provideContext and connectToStores, your component is wrapped. You have done it right to find the component using TestUtils. findRenderedDOMComponentWithClass, Simply use the foundComponent for test, that is what is being tested. i.e.
...
var foundComponent = TestUtils.findRenderedDOMComponentWithClass(
component, 'login-form');
expect(foundComponent.state).toEqual(initialState);
...
If you're still looking for a solution:
var tbg = React.createElement(x, { di: serviceLocator });
var renderer = React.addons.TestUtils.createRenderer();
var rtbg = renderer.render(tbg);
Then your method is here:
renderer._instance._instance.myMethod
Where myMethod is a function member of component x