I'm trying to test a simple component, that looks like this
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import styles from './styles.css'
export class App extends PureComponent {
handleClick = (event) => {
const { loadGreetings } = this.props
loadGreetings()
}
render () {
const { hi } = this.props
return (
<section>
<h1 className={styles.earlyDawn}>{hi}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
)
}
}
App.propTypes = {
hi: PropTypes.string,
loadGreetings: PropTypes.func
}
export default App
Here is my test file
import React from 'react'
import {App} from './index'
import {shallow} from 'Enzyme'
describe('Testing App container...', () => {
let props
beforeEach(() => {
props = {
loadGreetings: jest.fn().mockName('loadGreetings'),
hi: 'Hi from test'
}
})
test('should handle click on the button', () => {
const wrapper = shallow(<App {...props}/>)
const buttonHi = wrapper.find('button')
const instance = wrapper.instance()
expect(buttonHi.length).toBe(1)
jest.spyOn(instance, 'handleClick')
buttonHi.simulate('click')
expect(props.loadGreetings).toHaveBeenCalled()
expect(instance.handleClick).toHaveBeenCalled()
})
})
So the problem is in the second toHaveBeenCalled assertion that fails all the time. However, first toHaveBeenCalled seems to be working, which bothers me, because props.loadGreetings is called inside instance.handleClick. Could you please help me to find what may be the problem?
Dependencies: "react": "16.9.0", "react-dom": "16.9.0", "babel-jest": "^24.8.0", "enzyme": "^3.10.0", "jest": "^24.8.0",
A simpler approach would be to pass in some initial props and test your component based upon those initial props -- you'll also manipulate those props to add more assertions.
Here's a working example (click on the Tests tab to run the tests):
components/App/index.js
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
class App extends PureComponent {
handleClick = () => {
this.props.loadGreetings();
};
render() {
const { message } = this.props;
return (
<section className="app">
<h1>{message}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
);
}
}
App.propTypes = {
message: PropTypes.string,
loadGreetings: PropTypes.func
};
export default App;
components/App/App.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "./index";
// define the passed in function here for easier testing below
const loadGreetings = jest.fn();
// initial props to pass into 'App'
const initProps = {
message: "hi",
loadGreetings
};
describe("Testing App container...", () => {
let wrapper;
beforeEach(() => {
// this resets the wrapper with initial props defined above
wrapper = shallow(<App {...initProps} />);
});
afterEach(() => {
// this clears any calls to the mocked function
// and thereby resets it
loadGreetings.mockClear();
});
it("renders without errors", () => {
expect(wrapper.find(".app").exists()).toBeTruthy();
expect(loadGreetings).toHaveBeenCalledTimes(0);
});
it("initially renders a 'hi' message and then a 'goodbye' message", () => {
expect(wrapper.find("h1").text()).toEqual("hi");
// manipulating the initial 'message' prop
wrapper.setProps({ message: "goodbye" });
expect(wrapper.find("h1").text()).toEqual("goodbye");
});
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
// since we passed in 'loadGreetings' as a jest function
// we expect it to be called when the 'Handshake' button is
// clicked
wrapper.find("button").simulate("click");
expect(loadGreetings).toHaveBeenCalledTimes(1);
});
});
Not recommended (see below), but you can spy on the class method -- you'll have to work with instances, forceUpdate the instance, then invoke the handleClick method either manually, wrapper.instance().handleClick(), or indirectly via some element's event handler: wrapper.find("button").simulate("click") or wrapper.find("button").props().onClick().
The reason I don't recommend this testing strategy is that you're testing a React implementation (testing whether or not the element's event handler invokes your callback function). Instead, you can avoid that by asserting against whether or not a prop function is called and/or a prop/state change happens. This is a more standard and direct approach to testing the component -- as that is what we care about; we care that the props and/or state changes based upon some user action. In other words, by making assertions against the 'loadGreetings' prop being called we're already testing that the onClick event handler works.
Working example:
App.test.js (same testing as above, with the exception of this test):
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
const spy = jest.spyOn(wrapper.instance(), "handleClick"); // spying on the method class
wrapper.instance().forceUpdate(); // required to ensure the spy is placed on the method
wrapper.find("button").simulate("click");
expect(spy).toHaveBeenCalledTimes(1);
const mockedFn = jest.fn(); // setting the method as a mocked fn()
wrapper.instance().handleClick = mockedFn;
wrapper.instance().forceUpdate(); // required to update the method instance with a mocked fn
wrapper.find("button").simulate("click");
expect(mockedFn).toHaveBeenCalledTimes(1);
});
Related
given code of a component with a helper function defined inside the component. how do i return whatever value i want to return with jest and react testing library?
import { act, render } from "#testing-library/react";
import Comp, * as Module from "./helper";
describe("helper", () => {
// why this one dosen't work?
test("comp with mock", () => {
let component;
const spy = jest.spyOn(Module, "PrintTest"); // dosen't work: .mockResolvedValue("Bob");
spy.mockReturnValue("bob");
// console.log(screen.debug());
act(() => {
component = render(<Comp />);
});
expect(component.getByTestId("original-data")).toBe("Bob");
});
});
component
export function PrintTest(data) {
return <div data-testid="original-data">{data}</div>;
}
export default function Comp() {
return <div>test with: {PrintTest("data from component")}</div>;
}
How do i change the PrintTest to print something else other than "data from component". and maybe "Bob" or whatever?
Thanks!
codesandbox example:
https://codesandbox.io/s/holy-bash-qf7pq8?file=/src/helper.js:27-28
I have a simple react component.
import React, { Component } from "react";
class SampleText extends Component {
handleChange = e => {
console.log(" perform other task");
this.otherTask();
};
render() {
return <input type="text" onChange={this.handleChange} id="text1" />;
}
}
export default SampleText;
I want to test if i change something in the input field the handleChange method is called.
This is what i have tried:
import React from "react";
import SampleText from "./SampleText";
import Adapter from "enzyme-adapter-react-16";
import { shallow, configure } from "enzyme";
configure({ adapter: new Adapter() });
test("input change handle function", () => {
const wrapper = shallow(<SampleText />);
const instance = wrapper.instance();
jest.spyOn(instance, "handleChange");
//simulate instance's onChange event here
wrapper.find('input[id="text1"]').simulate("change", {
target: { value: "some random text" }
});
expect(instance.handleChange).toHaveBeenCalled();
});
The problem is when i simulate the change it is actually entering in the original handleChange method. The error that i get:
TypeError: this.otherTask is not a function
How can i achieve this simple test? Maybe i have to simulate the change of the instance's input field rather than the wrappers but don't have a clue of how to do it.
I solved it after adding some little changes to my test code :)
test("input change handle function", () => {
const wrapper = shallow(<SampleText />);
const instance = wrapper.instance();
//here is the change.
const spy = jest.spyOn(instance, "handleChange").mockImplementation(() => {});
wrapper.instance().forceUpdate();
//simulate instance's onChange event here
wrapper.find('input[id="text1"]').simulate("change", {
target: { value: "some random text" }
});
expect(spy).toHaveBeenCalled();
});
so i had to add an empty mockImplementation and use
wrapper.instance().forceUpdate();
to make it work.
I just started doing some unit testing for React JS - using Jest / enzyme.
I would like to know which test (format) is more useful and correct to use for future tests. These are 2 different tests that I'm working on it at the moment.
Unit test 1 : I was writing most of my tests based on this set up
import React from 'react';
import Enzyme from 'enzyme';
import { shallow} from 'enzyme';
import EditWorkflow from './EditWorkflow';
import Adapter from 'enzyme-adapter-react-15';
//render must import shallow = method to show object structure
//Unit Test V
Enzyme.configure({ adapter: new Adapter() })
describe ('<Workflow />', () => {
it( 'renders 1 <Workflow /> Component', () => {
const Component = shallow(<EditWorkflow name= "workflow"/>);
expect(Component).toHaveLength(1);
});
describe('It renders props correctly', () => {
const Component = shallow(<EditWorkflow name= "workflow"/>);
expect(Component.instance().props.name).toBe('workflow');
})
});
**Unit test 2
Different way to write an unit test****
import React from 'react';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-15';
import { Login } from './App';
import renderer from 'react-test-renderer';
Enzyme.configure({adapter: new Adapter()});
let wrapper;
let defaultProps = {
getSessionContext: jest.fn(),
checkSession: jest.fn(),
}
let mockCheckSession;
describe('Login', () => {
beforeEach(() => {
mockCheckSession = jest.fn(()=>{return true})
defaultProps = {
getSessionContext: jest.fn(),
checkSession: mockCheckSession,
}
})
it('should render "authorizing..." if theres no session ', () => {
mockCheckSession = jest.fn(()=>{return false})
defaultProps.checkSession = mockCheckSession;
const tree = renderer
.create(<Login {...defaultProps} />)
.toJSON();
expect(tree).toMatchSnapshot();
})
it('should render null if there is a session ', () => {
mockCheckSession = jest.fn(()=>{return true})
defaultProps.checkSession = mockCheckSession;
const tree = renderer
.create(<Login {...defaultProps}/>)
.toJSON();
expect(tree).toMatchSnapshot();
})
})
Since you're not providing the full code, it's hard to help you with your current component. Here are some general tips:
One of the goals of writing (good) unit tests for your React components, is to make sure your component behaves and renders as you want it to do. What I usually do, in this part there is no right or wrong, is start reading the render function from top to bottom and take note of each logical part.
Example #1:
Simply test if the className is set on the right element.
class Screen extends Component {
render() {
return (
<div className={this.props.className}>
<h1>My screen</h1>
</div>
);
}
}
it('should set the className on the first div', () => {
const wrapper = shallow(<Screen className="screen" />);
expect(wrapper.hasClass('screen')).toBeTruthy();
});
Example #2:
If the component renders a part conditionally, you want to test both cases.
class Screen extends Component {
render() {
return (
<div className={this.props.className}>
<h1>My screen</h1>
{this.props.subheading ? <h4>{this.props.subheading}</h4> : null}
</div>
);
}
}
it('should not render the subheading when not given by prop', () => {
const wrapper = shallow(<Screen />);
expect(wrapper.find('h4').exists()).toBeFalsy();
});
it('should render the subheading when given by prop', () => {
const wrapper = shallow(<Screen subheading="My custom subheading!" />);
expect(wrapper.find('h4').exists()).toBeTruthy();
expect(wrapper.find('h4').text()).toEqual('My custom subheading!');
});
I can give some more examples, but I think you'll get the idea.
I'm trying to attempt some TDD and I thought I'd start with a simple login component. I'm struggling to test the function on a submit button.
I can test that the button is rendered and that when it is clicked that state is updated, so the function is being called in the component. The test can see the state is updated, but not that the function is being called.
I've boiled my code down to the most basic functionality that I can, I must not be able to see something! I removed all of the other tests for the Login Component so that I can try to narrow down the problem. I've tried a shallow() instead of a mount() but no joy. I want to write a more complicated test but I'm failing at even recognising that the fn has been called.
My test:
import React from 'react';
import { mount } from 'enzyme';
import Login from './Login';
describe('when clicking the `submit` button', () => {
const mockSubmit = jest.fn();
const props = {
handleBtnClick: mockSubmit
};
const wrapper = mount(<Login {...props} />);
beforeEach(() => {
wrapper.find('button.btn-submit').simulate('click');
});
it('renders the `submit` button', () => {
expect(wrapper.find('button.btn-submit')).toExist();
});
it('when the submit button is clicked it updates `state`', () => {
expect(wrapper.state().buttonClicked).toEqual(true);
});
it('calls the submit callback', () => {
expect(mockSubmit).toHaveBeenCalled();
});
});
My Component:
import React, { Component } from 'react';
class Login extends Component {
constructor() {
super();
this.state = {
buttonClicked: false
}
}
handleBtnClick = (e) => {
e.preventDefault();
this.setState({
buttonClicked: true
})
}
render() {
console.log(this.state.buttonClicked);
return(
<div>
<h1>Login</h1>
<form>
<div className="form-row">
<button
type="submit"
className="btn-submit"
onClick={this.handleBtnClick}
>
Submit
</button>
</div>
</form>
</div>
);
}
}
export default Login;
Ok, so I needed to amend my search and add the keyword 'class method'. I was confused between the tutorials and articles I'd been reading. Using that I found this answer: How to mock React component methods with jest and enzyme
So the test:
it('calls the submit callback', () => {
expect(mockSubmit).toHaveBeenCalled();
});
should be:
it('it calls the handleBtnClick method', () => {
const mockSubmit= jest.fn();
wrapper.instance().handleBtnClick= mockSubmit;
wrapper.instance().handleBtnClick();
expect(mockSubmit).toHaveBeenCalled();
});
And the props can be removed as that was the wrong tree to be barking up.
I'm following the example from this stackoverflow answer - Test a React Component function with Jest. I have an example component and test set up. The component works correctly when loaded into App.js.
Component -
import React, { PropTypes, Component } from 'react';
export default class ExampleModule extends Component {
static propTypes = {
onAction: PropTypes.func,
}
static defaultProps = {
onAction: () => { console.log("In onAction"); }
}
doAction = () => {
// do something else
console.log('In do action');
this.props.onAction();
}
render() {
return(
<div>
<button className='action-btn' onClick= {this.doAction.bind(this)}>Do action</button>
</div>
)
}
}
And here's the test -
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import ExampleComponent from './ExampleModule.js';
let Example;
describe('Example component', function() {
beforeEach(function() {
Example = TestUtils.renderIntoDocument(<ExampleComponent />);
})
it('calls props functions', function() {
Example.doAction = jest.genMockFunction();
let actionBtn = TestUtils.findRenderedDOMComponentWithClass(Example, 'action-btn');
TestUtils.Simulate.click(actionBtn);
expect(Example.doAction).toBeCalled();
})
it('doAction calls onAction', function() {
expect(Example.props.onAction).not.toBeCalled();
Example.doAction();
expect(Example.props.onAction).toBeCalled();
})
})
However, I get the following error -
FAIL src/App/components/Example/ExampleModule.test.js
Console
console.log src/App/components/Example/ExampleModule.js:14
In do action
console.log src/App/components/Example/ExampleModule.js:24
In onAction
Example component › calls props functions
Expected the mock function to be called.
at Object.<anonymous> (src/App/components/Example/ExampleModule.test.js:17:30)
at process._tickCallback (node.js:369:9)
Example component › doAction calls onAction
toBeCalled matcher can only be used on a spy or mock function.
at Object.<anonymous> (src/App/components/Example/ExampleModule.test.js:21:40)
at process._tickCallback (node.js:369:9)
I can see the console.logs in the doAction and onAction are being called even when I want to mock out doAction.
Also, I'm unable to mock out onAction. I get this error -
TypeError: Cannot assign to read only property 'onAction' of #<Object>
I've tried jest.fn() but got the same errors.
How do I mock these functions and test them?
EDIT:
I was able to mock doAction by using jest.fn() in the following way -
let mockFn = jest.fn();
Example.doAction = mockFn()
However, I'm still unable to mock Example.props.onAction.
In your test when you render document, you need to pass a mock function which you can later watch if it was being called, something like this -
let onActionMock = jest.fn();
beforeAll(function() {
Example = TestUtils.renderIntoDocument(<ExampleComponent onAction={onActionMock}/>);
});
beforeEach(function() {
onActionMock.mockClear();
});
it('doAction calls onAction', () => {
Example.doAction();
expect(onActionMock).toBeCalled();
});
You can further test the function with what it has been called with -
expect(onActionMock).toBeCalledWith('Some argument');
Hope this helps.