window.history.back() not being called - jest enzyme - reactjs

I wrote a simple unit test for the following. I am new to React JS testing - Trying to run a test using jest and enzyme.
render() {
return (
<div>
<div className="not-found">
<div className='_2'>WAS NOT FOUND</div>
<div onClick={() => {window.history.back()}} className='not-found-
btn' href='/'>GO BACK</div>
)
}
}
The file looks simple, there are no props and the only thing not being covered when the test is running is onClick . How could I test onClick and make sure the test is 100 % covered. Thanks
<div onClick={() => {window.history.back()}} className='not-found-
btn' href='/'>GO BACK</div>
file.test.js
// jest mock functions (mocks this.props.func)
const onClick = jest.fn();
// defining this.props
const baseProps = {
onClick,
}
describe(' Test', () => {
let wrapper;
let tree;
beforeEach(() => wrapper = shallow(<Component{...baseProps } />));
// before each test, shallow mount the Component
it('should render correctly', () => {
tree = renderer.create(<NotFound {...baseProps} />)
let treeJson = tree.toJSON()
expect(treeJson).toMatchSnapshot();
tree.unmount()
});
it('calls onClick event ', () => {
const mockOnClick = jest.fn();
const wrapper = shallow(
<NotFound onClick={mockOnClick} className='not-found-btn' />
);
const component = wrapper.shallow();
component.find('GO BACK').simulate('click');
expect(mockOnClick.mock.calls.length).toEqual(1);

I'd avoid using window history and instead use react-router-dom for MPAs. In addition, instead of using an anonymous function, you can use a PureComponent class (it's similar to a Component class, but it doesn't update state) with a method class function.
Working example: https://codesandbox.io/s/j3qo6ppxqy (this example uses react-router-dom and has a mix of integration and unit testing -- see the tests tab at the bottom of the page to run the tests and look for __test__ folders to see the code)
components/NotFound/notfound.js
import React, { PureComponent } from "react";
import { Button } from "antd";
export default class NotFound extends PureComponent {
handlePageBack = () => this.props.history.push("/");
render = () => (
<div className="notfound">
<h1>404 - Not Found!</h1>
<Button type="default" onClick={this.handlePageBack}>
Go Back
</Button>
</div>
);
}
components/NotFound/__tests__/notfound.test.js (as mentioned here, you can also test the class method, if desired)
import React from "react";
import { shallowComponent } from "../../../tests/utils";
import NotFound from "../notfound";
const mockGoBack = jest.fn();
const initialProps = {
history: {
goBack: mockGoBack
}
};
/*
the shallowComponent function below is a custom function in "tests/utils/index.js" that
simplifies shallow mounting a component with props and state
*/
const wrapper = shallowComponent(<NotFound {...initialProps} />);
describe("Not Found", () => {
it("renders without errors", () => {
const notfoundComponent = wrapper.find("div.notfound");
expect(notfoundComponent).toHaveLength(1);
});
it("pushes back a page when Go Back button is clicked", () => {
wrapper.find("Button").simulate("click");
expect(mockGoBack).toHaveBeenCalled();
});
});

window.history.back is being called, but it has a delay time. I can make it work using a Promise:
const Component = ()=> (<div>
<button onClick={()=> window.history.back()} className="btn btn-back">
Back
</button>
</div>)
Component.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
const delayAction = (fn, time = 1000) =>
new Promise((resolve) => {
fn();
setTimeout(() => {
resolve();
}, time);
});
let container = null;
describe("App tests", () => {
afterEach(() => {
//unmount Component...
});
beforeEach(() => {
//mount Component
});
it("should call history.back()", async (done) => {
const btnBack = container.querySelector(".btn-back");
await act(() =>
delayAction(() => btnBack.dispatchEvent(new MouseEvent("click", { bubbles: true })))
);
// asserts..
done();
});
});

Related

I am using React Testing Library and Jest to test my app. I'm not sure how to test an imported function. I have the following code below:

As you can see, I have a function called trueFn that I export into App. I then call it every time I click the button. I'm just unsure how to get jest to mock that function in the test file. How can I make sure the mocked function is called when I click the button?
/*
./trueFn.js
*/
export const trueFn = () => {
return true;
}
/*
App.js
*/
import React from 'react';
import { trueFn } from './trueFn';
const App = () => {
return (
<Button onClick={trueFn}>Click me</Button>
)
}
/*
App.test.js
*/
const getComponent = () => {
return render (
<App />
)
}
describe('App.js Component', () => {
it('should run trueFn on button click', () => {
const mockTrueFn = jest.fn();
const component = getComponent();
expect(mockTrueFn).toHaveBeenCalledTimes(1);
})
})
You need to use a spy
Something like this should work:
const trueFn = require('./trueFn.js');
describe('App.js Component', () => {
it('should run trueFn on button click', () => {
const spy = jest.spyOn(trueFn);
getComponent();
// do an action to call your button, something like:
const button = screen.getByTestId('element');
userEvent.click(button);
expect(spy).toHaveBeenCalled();
})
})

React Js - Enzyme test if setState is triggered

I am using enzyme to test my react application. This is my react component:
import React from 'react';
const Child = ({name, setName}) => {
return (
<div>
<button onClick={() => setName('test')}>setName</button>
<h2>Name: {name}</h2>
</div>
);
};
export default Child;
By clicking the button i change the setName which is passed as bellow:
function App() {
const [name, setName] = useState();
return (
<div className="App">
<Child setName={setName} name={name}/>
</div>
);
}
export default App;
So, testing Child component i created the next test to test when user clicks the button should trigger setName.
describe('Should test Child component', () => {
const wrapper = (props) => shallow(<Child {...props}/>)
test('should trigger setName', () => {
const mockFun = jest.fn()
const r = wrapper({setName: jest.fn() });
const btn = r.find('button');
btn.simulate('click');
expect(r.props().setName).toHaveBeenCalled()
})
})
Running the test i get:
Error: expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has value: undefined
Why i get this error and how to make my test to be valid?
I can't test it right now but I think modifying it like this should do it
describe('Should test Child component', () => {
const wrapper = (props) => shallow(<Child {...props}/>)
test('should trigger setName', () => {
const mockFun = jest.fn()
const r = wrapper({setName: mockFun });
const btn = r.find('button');
btn.simulate('click');
expect(mockFun).toHaveBeenCalled()
})
})
Also see this one as a reference same issue How to test onClick props of a button using Jest and Enzyme

How to use jest.spyOn with functional react component

I want to test if the handleAddBookToCart function is called when I clicked on the button using Jest.spyOn.
Thank you in advance for your help.
const HomeComponent = props => {
const { addBookToCart } = props;
const handleAddBookToCart = id => {
addBookToCart(id);
};
return (
<button onClick={() => handleAddBookToCart(id)}></button>
)
}
//Container
const mapDispatchToProps = dispatch => ({
addBookToCart: idBook => dispatch(cartOperations.addBookToCart(idBook))
});
//file test
describe('Test home component', () => {
let container;
let wrapper;
let component;
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<HomeContainer />
</Provider>
);
container = wrapper.find(HomeContainer);
component = container.find(HomeComponent);
it('calls the handleAddBookToCart function when the button is clicked', () => {
const spy = jest.spyOn(???)
component.find('button').simulate('click');
expect(spy).toHaveBeenCalled();
});
Should you must use spyOn? You can just pass a jest mock fn as the addBookToCart prop
it('calls the handleAddBookToCart function when the button is clicked', () => {
const props = {
addBookToCart: jest.fn(),
...your other props
}
const component = shallow(<HomeComponent {...props} />)
component.find('button').simulate('click');
expect(props.addBookToCart).toHaveBeenCalled();
});
An easy way would be to send a mock into the property addBookToCart and spy on that when the button is clicked.
So when you create/mount/shallow your component for the unit test, try the following (mount coming from enzyme but feel free to use your existing method of testing components):
const spy = jest.fn();
const component = mount(<HomeComponent addBookToCart={spy} />);

Changes to mounted component not showing in enzyme even after timeout

how can i test the child component has mounted. Please see the below implmentation.
i have an api call, until the data resolves it will show a loader then it will show the actual component.
Using mount i need to simulate a click on the child component also. What am i doing wrong here.
Please see the below snippet.
// App.js
import React, {Component, Fragment} from 'react'
import Child from './child'
class App extends Component{
state = {
data: null,
enable: false
}
componentDidMount(){
this.getData()
}
getData = async () => {
const response = await fetch('http://www.example.com');
const data = await response.json();
this.setState({
data
})
}
_handleChildClick = () => {
this.setState({
enable: true
})
}
render(){
const {data, enable} = this.state
if(!data){
return (
<div>
Loading
</div>
)
}else{
<Fragment>
<Child
handleChildClick={this._handleChildClick}
/>
</Fragment>
}
}
}
export default App
import React from 'react';
const child = () => {
return(
<div>
<button
className="toggle"
onClick={props.handleChildClick}
>
Toggle
</button>
</div>
)
}
export default child
// App.test.js
import React from 'react';
import {enzyme} from 'enzyme';
import App from './App';
describe("App test cases", () => {
it('should trigger _handleChildClick', async () => {
window.fetch = jest.fn().mockImplementation(() => ({
status: 200,
json: () => new Promise((resolve, reject) => {
resolve(
{
name: "some data"
}
)
})
}))
const mountWrapper = await mount(<App />)
mountWrapper.update()
console.log("mountWrapper", mountWrapper.debug()) // showing the loader one
setTimeout(() => {
console.log("mountWrapper", mountWrapper.debug()) // nothing showing
// expect(mountWrapper.find('.toggle').length).toEqual(1)
},0)
})
})
You have to update your enzyme wrapper inside the timeout.
import React from 'react';
import {enzyme} from 'enzyme';
import App from './App';
describe("App test cases", () => {
it('should trigger _handleChildClick', async () => {
window.fetch = jest.fn().mockImplementation(() => ({
status: 200,
json: () => new Promise((resolve, reject) => {
resolve(
{
name: "some data"
}
)
})
}))
const mountWrapper = await mount(<App />)
mountWrapper.update()
console.log("mountWrapper", mountWrapper.debug()) // showing the loader one
setTimeout(() => {
//**An update required here
mountWrapper.update();
console.log("mountWrapper", mountWrapper.debug()) // nothing showing
// expect(mountWrapper.find('.toggle').length).toEqual(1)
},0)
})
})
Live example created here: https://codesandbox.io/s/5083l6vmjk

Enzyme onclick spy toHaveBeenCalled test does not work when testing on arrow function

how can i test the child component onclick.
Please see the below snippet.
// App.js
import React, {Component, Fragment} from 'react'
import Child from './child'
class App extends Component{
state = {
data: null,
enable: false
}
componentDidMount(){
this.getData()
}
getData = async () => {
const response = await fetch('http://www.example.com');
const data = await response.json();
this.setState({
data
})
}
_handleChildClick = () => {
this.setState({
enable: true
})
}
render(){
const {data, enable} = this.state
if(!data){
return (
<div>
Loading
</div>
)
}else{
<Fragment>
<Child
handleChildClick={this._handleChildClick}
/>
</Fragment>
}
}
}
export default App
import React from 'react';
const child = () => {
return(
<div>
<button
className="toggle"
onClick={props.handleChildClick}
>
Toggle
</button>
</div>
)
}
export default child
// App.test.js
import React from 'react';
import {enzyme} from 'enzyme';
import App from './App';
describe("App test cases", () => {
it('should trigger _handleChildClick', async () => {
window.fetch = jest.fn().mockImplementation(() => ({
status: 200,
json: () => new Promise((resolve, reject) => {
resolve(
{
name: "some data"
}
)
})
}))
const mountWrapper = await mount(<App />)
setTimeout(() => {
mountWrapper.update()
const SpyhandleChildClick = jest.spyOn(mountWrapper.instance(),'_handleChildClick')
mountWrapper.find('.toggle').simulate('click')
expect(SpyhandleChildClick).toHaveBeenCalled() // not called
},0)
})
})
Some important points to consider.
Asynchronous code in your tests
If you have to do asynchronous tasks in your tests you always have to await until the asynchronous stuff is completed.
setTimeout(() => {
mountWrapper.update()
const SpyhandleChildClick = jest.spyOn(mountWrapper.instance(),'_handleChildClick')
mountWrapper.find('.toggle').simulate('click')
expect(SpyhandleChildClick).toHaveBeenCalled() // not called
},0)
Above in your code you have a timeout segment. Any test condition inside this code block will not be evaluated since by the time they are evaluated you 'test session' will already be over due to the aync nature.
Testing arrow functions in React with enzyme - forceUpdate()
There seem to be a problem with the enzyme library where you have to force update the react component after spying for it to latch on to the method.
Please follow the github issue for more information : https://github.com/airbnb/enzyme/issues/365
I also cleaned up your test code a bit to make it more understandable!
// App.test.js
import React from 'react';
import {enzyme} from 'enzyme';
import App from './App';
describe("App test cases", () => {
it("should trigger _handleChildClick", async () => {
window.fetch = jest.fn().mockImplementation(() => ({
status: 200,
json: () =>
new Promise((resolve, reject) => {
resolve({
name: "some data"
});
})
}));
const mountWrapper = mount(<App />);
mountWrapper.update();
console.log("mountWrapper", mountWrapper.debug()); // showing the loader one
//[FIX]This code will block and wait for your asynchronous tasks to be completed
await new Promise(res => setTimeout(() => res(), 0));
mountWrapper.update();
console.log("mountWrapper", mountWrapper.debug()); // nothing showing
expect(mountWrapper.find(".toggle").length).toEqual(1);
//[FIX]Get a reference from the wrapper and force update after the spyOn call
const instance = mountWrapper.instance();
const spy = jest.spyOn(instance, "_handleChildClick");
instance.forceUpdate();
mountWrapper.find(".toggle").simulate("click");
expect(spy).toHaveBeenCalled();
});
});
Live Demo Link: Click on the 'Tests' tab on the browser to see the test results
https://codesandbox.io/s/mz21kpm37j

Resources