I'm trying to test, by passing mock data the on change method for App.test.js, however im getting the following error.
● Should handle onChange event › should handle onChange event
expect(received).toEqual(expected)
Expected value to equal:
"Owl"
Received:
undefined
Difference:
Comparing two different types of values. Expected string but received undefined.
i checked out a similar post
onChange - Testing using Jest Enzyme - check?, however their wasn't an answer that could help
App.js
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Card from './Card';
import PropTypes from "prop-types";
const Styles = {
marginTop: '100px',
inputStyle: {
borderRadius: '0px',
border: 'none',
borderBottom: '2px solid #000',
outline: 'none',
focus: 'none'
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
title: undefined,
url: undefined
}
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.setState({query: e.target.value})
}
getGIY = async(e) => {
e.preventDefault();
const { query } = this.state;
await fetch(`http://api.giphy.com/v1/gifs/search?q=${query}&api_key=iBXhsCDYcnktw8n3WSJvIUQCXRqVv8AP&limit=5`)
.then(response => response.json())
.then(({ data }) => {
this.setState({
title: data[0].title,
url: data[0].images.downsized.url
});
})
.catch(console.log);
}
render() {
return (
<div className="col-md-6 mx-auto" style={Styles}>
<h1 className="gif-title">Random GIF fetch</h1>
<form className="form-group" onSubmit={this.getGIY}>
<input
style={Styles.inputStyle}
className="form-control"
type="text"
name="query"
onChange={this.onChange}
placeholder="Search GIF..."/>
<button type="submit" className="btn btn-primary mt-4">Get GIF</button>
</form>
<Card title={this.state.title} url={this.state.url}/>
</div>
);
}
}
PropTypes.propTypes = {
onChange: PropTypes.func.isRequired,
getGIY:PropTypes.func.isRequired,
title:PropTypes.string.isRequired,
url:PropTypes.string.isRequired
}
export default App;
App.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import {shallow} from 'enzyme';
import App from './App';
describe('Should render App Component', ()=> {
it('should render app component', ()=> {
const component = shallow(<App />);
})
})
describe('Should have h1 title', ()=> {
it('Should show Random GIF fetch', ()=>{
const component = shallow(<App/>);
expect(component.find("h1.gif-title")).toHaveLength(1);
expect(component.find("h1.gif-title").text()).toContain("Random GIF fetch")
})
})
describe('Should handle onChange event', ()=> {
it('should handle onChange event', ()=> {
const component = shallow(<App/>)
const form = component.find('input')
form.props().onChange({
target:{
title: 'Owl',
query: 'Owl',
url: 'https://media.giphy.com/media/qISaMW1xwmvNS/giphy.gif'
}
});
expect(component.state('query')).toEqual('Owl')
})
})
Your event handler sets the state based on e.target.value:
onChange(e) {
this.setState({query: e.target.value})
}
...but you're not passing anything for target.value in your mocked event.
Change your test to this:
describe('Should handle onChange event', ()=> {
it('should handle onChange event', ()=> {
const component = shallow(<App/>)
const form = component.find('input')
form.props().onChange({
target:{
value: 'Owl'
}
});
expect(component.state('query')).toEqual('Owl') // Success!
})
})
...and it should work.
Related
Beginner with react testing,
I am using jest and react testing library, here I have a component 'A' which is a modal, I'm trying to implement a test to it, when the user clicks a button 'Delete Link' then this modal should disappear(function onDelete). As you can see I'm clicking the button using FireEvent.click() so after it when changing toHaveBeenCalledTimes(0) from 0 to 1, I'm getting Expected number of calls: 1 Received number of calls: 0, shouldn't be expected and received both be 1?
The end component(modal) should not be visible to the user after clicking Delete Link.
Can someone enlighten me with this?
Any suggestions/help is appreciated.
English is not my mother language so there might be mistakes.
my code:
import React from "react";
import { render, screen, cleanup, fireEvent } from "#testing-
library/react";
import { LinkForm } from "../forms/LinkForm";
import { Provider } from "react-redux";
import { store } from "../../redux/store";
import "#testing-library/jest-dom/extend-expect";
describe("Testing component", () => {
const onClickCallback = jest.fn();
test("Testing if link is deleted when button 'Delete Link' is clicked", () => {
const mockDelete = jest.fn();
const props = {
onDelete: mockDelete,
};
render(
<Provider store={store}>
<LinkForm
classes={{ button_basic: "", formControl: "" }}
key={""}
onSubmit={onClickCallback}
onCancel={onClickCallback}
// onClick={onClickCallback()}
{...props}
/>
</Provider>
);
const component = screen.getByTestId("LinkForm");
const deleteLinkButton = screen.getByRole("button", {
name: /Delete Link/i,
});
expect(deleteLinkButton).toBeVisible();
fireEvent.click(deleteLinkButton);
expect(mockDelete).toHaveBeenCalledTimes(0);
expect(component).toBeVisible();
});
});
import React, { useEffect, useState } from "react";
import { connect, RootStateOrAny, useDispatch, useSelector } from "react-redux";
import { Trans } from "react-i18next";
import { editLink, changeLink, removeLink } from "../../redux/actions";
import {Button} from "#material-ui/core/";
import { Done, Delete } from "#material-ui/icons";
interface AFormProps {
key: string;
onSubmit: () => void;
onCancel: () => void;
onClick?: () => void;
classes: {
button_basic: string;
formControl: string;
};
}
const A: React.FC<AFormProps> = (props) => {
const dispatch = useDispatch();
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
dispatch(editLink(linkSettings));
externalOnSubmit();
};
const onDelete = () => {
// Delete selected link from graph
dispatch(removeLink(currentLink.id));
dispatch(changeLink(""));
};
const disabled = currentLink ? false : true;
return (
<form onSubmit={handleSubmit} data-testid="LinkForm">
<Button
id="delete"
type="button"
onClick={onDelete}
disabled={disabled}
variant="outlined"
color="secondary"
className={classes.button_basic}
startIcon={<Delete />}
>
<Trans i18nKey="form.linkForm.delete">Delete Link</Trans>
</Button>
</form>
);
};
export const LinkForm = connect(null, null)(A);
In the test, you are passing a mock function to the onDelete prop of the form but this is not defined in AFormProps nor is an onDelete prop being consumed in the component. The onDelete function is created within the component scope and set as the onClick for the button. The mock function will never be used in this case.
I am trying to re-render the page based on a button click. I have the function updateCowList which calls setState() in my app component. The handleClick logic is in my newCow component which handles the button and the text input.
The console.logs() that I am seeing are 'fire', but I am not seeing the 'after' console.log(), nor am I seeing any of the logs within my updateCowList function in App.
How can I get my updateCowList function to run? I have tried calling it in all sorts of ways, destructuring props, etc.
Here is my App:
import React from 'react';
import CowList from './CowList.jsx';
import CowListEntry from './CowListEntry.jsx';
import axios from 'axios';
import SearchDB from './searchDB.js';
import NewCow from './NewCow.jsx';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cows: []
}
// this.updateCowList = this.updateCowList.bind(this);
}
componentDidMount() {
SearchDB()
.then((res) => {
this.setState({cows: res.data})
}, (err) => {
console.log(err);
});
}
updateCowList(cow) {
console.log('update cow list is running')
oldCows = [...this.state.cows];
newCows = oldCows.push(cow);
console.log('new cows be4 set state', newCows);
this.setState({cows: newCows});
console.log('new cows after set state', newCows);
}
render() {
return (
<div>
<CowList cows={this.state.cows}/>
<NewCow props={this.updateCowList}/>
</div>
)
}
}
export default App;
here is my NewCow component:
import React from 'react';
import axios from 'axios';
class NewCow extends React.Component {
constructor(props) {
super(props);
this.state = {
entry: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
let split = this.state.entry.split(', ')
console.log(split)
axios.post('http://localhost:3000/api/cows', {
name: split[0],
description: split[1]
})
.then(res => { console.log('fire', res.data);
this.props.updateCowList(res.data);
console.log('after')
})
.catch(err => 'error submitting cow :( mooooo');
}
handleChange (event) {
this.setState({entry: event.target.value})
}
render () {
return (
<div className='newCowForm'>
<input className='form-control' type='text' onChange={this.handleChange} value={this.state.entry} placeholder={'name, description'} />
<button onClick={this.handleClick} className='newCowButton'>Create new cow</button>
</div>
)
}
}
export default NewCow;
<NewCow props={this.updateCowList}/>
should be :
<NewCow updateCowList={this.updateCowList}/>
I am trying to test a component that use context. After I mount it (shallow does not work with useContext apparently) I am trying to set default values for the component data.
I was expecting const contextValues = { text: 'mock', msg: 'SUCCESS' }; and passing that to the AlertContextProvider to set a state for that component but I am probably looking at this the wrong way.
AlertContext.js:
import React, { createContext, useState, useContext } from 'react';
export const AlertContext = createContext();
const AlertContextProvider = props => {
const [alert, setAlert] = useState({
text: '',
msg: ''
});
const updateAlert = (text, msg) => {
setAlert({
text,
msg
});
};
return (
<AlertContext.Provider value={{ alert, updateAlert }}>
{props.children}
</AlertContext.Provider>
);
};
export default AlertContextProvider;
Alert.js (component):
import React, { useContext } from 'react';
import './Alert.scss';
import { AlertContext } from '../context/AlertContext';
const Alert = () => {
const { alert } = useContext(AlertContext);
return (
<div className='alert'>
<p className="alert-para">{alert.text}</p>
</div>
);
};
export default Alert;
Alert.js(text)
import React from 'react';
import { mount } from 'enzyme';
import Alert from '../components/Alert';
import AlertContextProvider from '../context/AlertContext';
describe('Alert', () => {
let wrapper;
beforeEach(() => {
const contextValues = { text: 'mock', msg: 'SUCCESS' };
// Below mounting is needed as Enzyme does not yet support shallow mocks
wrapper = mount(
<AlertContextProvider value={contextValues}>
<Alert />
</AlertContextProvider>
);
});
test('Should render a paragraph', () => {
const element =wrapper.find('.alert-para');
expect(element.length).toBe(1); // this is correct
expect(element.text()).toEqual('mock'); // THIS FAILS AS THE VALUE OF THE ELEMENT IS AN EMPTY STRING WHILE I WAS EXPECTING 'mock'
});
});
You are passing your contextValues through value prop on <AlertContextProvider /> but you are never using that prop to initialize data inside your context provider.
In this example, I used useEffect hook as componentDidMount to initialize your state AlertContext.js`
const AlertContextProvider = props => {
const [alert, setAlert] = useState({
text: '',
msg: ''
});
// The same as component did mount
useEffect(() => {
setAlert({
text: props.value.text,
msg: props.value.msg
})
}, [])
const updateAlert = (text, msg) => {
setAlert({
text,
msg
});
};
return (
<AlertContext.Provider value={{ alert, updateAlert }}>
{props.children}
</AlertContext.Provider>
);
};
You should use useCallback hook for your updateAlert function to memoize it.
How can we write the test to check defaultProps function in this case (
handleChange: () => {},handleBlur: () => {},handleSubmit: () => {}) attached to dom and they are working correctly?
I know we can test function when we pass as props but looking for help to test default functions. Thanks,
import React from 'react';
import PropTypes from 'prop-types';
const LoginForm = ({
handleChange,
handleBlur,
handleSubmit,
}) => (
<form onSubmit={handleSubmit}>
<input
onChange={handleChange}
onBlur={handleBlur}
/>
<input
onChange={handleChange}
onBlur={handleBlur}
/>
<button type="submit">
Submit
</button>
</form>
);
const shape = { email: '', password: '', generic: '' };
LoginForm.propTypes = {
handleChange: PropTypes.func,
handleBlur: PropTypes.func,
handleSubmit: PropTypes.func,
};
LoginForm.defaultProps = {
handleChange: () => {},
handleBlur: () => {},
handleSubmit: () => {},
};
export default LoginForm;
If you want to test that the functions have the expected behavior, you can access them as static properties on the LoginForm, and write your tests around that.
import LoginForm from 'wherever'
test('should have default handleChange', () => {
expect(LoginForm.defaultProps.handleChange).toBeDefined();
});
test('something something', () => {
const result = LoginForm.defaultProps.handleChange();
expect(result).toBe('something');
});
If you want to test that React does what it says it does -- ie, that it will pass the default props in if no other props are specified -- i recommend you don't. The creators of react have written tests around that.
You can access the props using mount and .props() from enzyme.
import LoginForm from '../somelocation'
import { mount } from 'enzyme'
it('should handleChange when value 'foo' is passed', () => {
const props = mount(<LoginForm />).props()
const handleChange = props.handleChange
expect(handleChange('foo')).to.equal('bar')
}
the const defaultProps should give you
props = {
handleChange: () => {},
handleBlur: () => {},
handleSubmit: () => {}
}
Hope this helps.
The answer posted by #Daniel Varela is good but could be more clear. Here is how I solved this issue.
I used just what he suggested mount and .props but the difference is that I wanted to test if some properties and functions were previously defined.
// React & Libraries
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
// Features
import ModalConfirmation from './modalConfirmation';
describe('ModalConfirmation', () => {
const wrapper = mount(<ModalConfirmation />);
it('should have default props', () => {
const props = wrapper.props();
expect(props).not.be.equal(null);
expect(props.data).to.deep.equal({});
expect(props.onCancel).to.not.throw();
expect(props.onConfirm).to.not.throw();
expect(props.cancelButtonText.length).be.greaterThan(0);
expect(props.confirmButtonText.length).be.greaterThan(0);
expect(props.confirmationMessage).be.equal(null);
});
});
This is how my Messenger Component looks like. As you can see there is the main component and a list component. The main component is exported as default.
With this everything is working as expected in my application.
/imports/ui/components/messenger.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Container, Segment, Loader, Header } from 'semantic-ui-react'
class Messenger extends Component {
static get propTypes () {
return {
data: PropTypes.array,
articleId: PropTypes.string,
isLoading: PropTypes.bool
}
}
render () {
const { data, articleId, isLoading } = this.props
if (isLoading) { return (<Loader active inverted size='massive' className='animated fadeIn' />) }
if (articleId) { return (<MessengerList data={data} articleId={articleId} />) }
return (
<Container>
<Segment id='' className='m-b-1'>
<Header as='h1'>Title</Header>
<MessengerList data={data} />
</Segment>
</Container>
)
}
}
class MessengerList extends Component {
/* ... */
}
export default Messenger
Now I would like to do some unit testing for the main component using enzyme. This is how I am doing it, but the last test is failing as MessengerList is not defined. So how should this be done.
import React from 'react'
import { expect } from 'meteor/practicalmeteor:chai'
import { shallow } from 'enzyme'
import { Container, Loader } from 'semantic-ui-react'
import Messenger from '/imports/ui/components/messenger.jsx'
describe('<Messenger />', () => {
const defaultProps = {
data: [],
articleId: '',
isLoading: true
}
it('should show <Loader /> while loading data', () => {
const wrapper = shallow(<Messenger {...defaultProps} />);
expect(wrapper.exists()).to.be.true
expect(wrapper.find(Loader).length).to.equal(1)
})
it('should show <Container /> data has been loaded', () => {
defaultProps.isLoading = false
const wrapper = shallow(<Messenger {...defaultProps} />);
expect(wrapper.find(Container).length).to.equal(1)
expect(wrapper.find(Loader).exists()).to.be.false
})
it('should show <MessengerList /> if articleID is given', () => {
defaultProps.articleID = 'dummy'
defaultProps.isLoading = false
const wrapper = shallow(<Messenger {...defaultProps} />);
expect(wrapper.find(MessengerList).length).to.equal(1)
expect(wrapper.find(Loader).exists()).to.be.false
})
})
I do not want to change export default Messenger
Export your MessengerList class ....
export class MessengerList extends Component {
/* ... */
}
And then in the test do ....
import React from 'react'
import { expect } from 'meteor/practicalmeteor:chai'
import { shallow } from 'enzyme'
import { Container, Loader } from 'semantic-ui-react'
import Messenger, { MessengerList } from '/imports/ui/components/messenger.jsx';
describe('<Messenger />', () => {
let wrapper;
const defaultProps = {
data: [],
articleId: '',
isLoading: true
}
beforeEach(() => {
// render the component once up here in this block. It runs before each test.
wrapper = shallow(<Messenger {...defaultProps} />);
});
it('should show <Loader /> while loading data', () => {
expect(wrapper.exists()).to.be.true
expect(wrapper.find(Loader).length).to.equal(1)
});
it('should show <Container /> data has been loaded', () => {
defaultProps.isLoading = false
expect(wrapper.find(Container).length).to.equal(1)
expect(wrapper.find(Loader).exists()).to.be.false
});
it('should show <MessengerList /> if articleID is given', () => {
defaultProps.articleID = 'dummy'
defaultProps.isLoading = false
expect(wrapper.find(MessengerList).length).to.equal(1);
expect(wrapper.find(Loader).exists()).to.be.false
});
});
UPDATE
Ideally, you should state that a prop is being modified first ...
...
describe('and the data has loaded', () => {
beforeEach(() => {
defaultProps.isLoading = false;
});
it('should show <Container /> component', () => {
expect(wrapper.find(Container).length).to.equal(1)
expect(wrapper.find(Loader).exists()).to.be.false
});
});
...