Hi Recently I'm testing my react application with jest+enzyme,
and it makes me really confused to write a test with useState or useEffect..
so this is my code and I want to test when user click the button, description is shows or not.(by change state value)
const Job = ({ job }) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = (id) => {
setIsOpen(!isOpen);
};
return (
<Card>
<Card.Text>
<Button id="button" onClick={toggle} variant="primary">
{!isOpen ? "View Detail" : "Hide Detail"}
</Button>
</Card.Text>
{isOpen && (
<div className="mt-4">
<ReactMardown source={job.description} />
</div>
)}
</Card>
);
};
export default Job;
Job.test.js
import React from "react";
import Adapter from "enzyme-adapter-react-16";
import { mount, shallow, configure } from "enzyme";
import Job from "./Job";
configure({ adapter: new Adapter() });
describe("when user click the button state value should changed", () => {
const job = jest.fn();
let wrapper;
beforeEach(() => {
wrapper = mount(<Job job={job} />);
});
it("should render", () => {
expect(wrapper).not.toBeNull();
});
test("user click button", () => {
const setIsOpen = jest.fn();
wrapper.find("#button").at(1).simulate("click");
expect(setIsOpen).toHaveBeenLastCalledWith({ isOpen: false });
});
});
but it only passed the first test..
#nambk I'd go with testing the style, eg. some component's background-color:
expect(component.find('#item-id').prop('style')).toHaveProperty('backgroundColor' 'black');
It's generally better, especially in stateless components, not to test the implementation, but its result.
it('user click button', () => {
expect(wrapper.find(ReactMardown)).toHaveLength(0);
wrapper.find('#button').simulate('click');
expect(wrapper.find(ReactMardown)).toHaveLength(1);
});
Related
I have React Native project. I am new to React. I was told not to put functions in the jsx as it's bad practice. Looking for guidance to keep component clean
// App.js
export const App = () => {
// other things
return (
<CustomButton onButtonPress={()=> someFunction} />
)
}
Then my custom component that I am in question. Should I create a functions which will included the prop or do I use the prop directly in the jsx
export const CustomButton = (props) => {
<Button
onPress={() => props.onButtonPress(item.socketId)}>
Decline
</Button>
or
export const CustomButton = (props) => {
const handleDisconnect = (socketId) => {
props.onButtonPress(socketId)
}
<Button
onPress={() => handleDisconnect(item.socketId)}>
Decline
</Button>
Long story short. React use function component, if you not use memo, your function will be create again and take some memory and time. Also arrow function will recreate too.
import React, { memo, useCallback } from "react";
import { Text, Button } from "react-native";
const ClearCustomButtom = memo(({ onPress, title }) => (
<Button onPress={onPress}>
<Text>{title}</Text>
</Button>
));
const ExtraCustomButtom = memo(({ onPress, title, socketId }) => {
const handlePress = () => {
console.log("Some extra action");
onPress(socketId);
};
return (
<Button onPress={handlePress}>
<Text>{title}</Text>
</Button>
);
});
const RequestCustomButtom = memo(({ url, title }) => {
const handlePress = () => {
//send request on server
};
return (
<Button onPress={handlePress}>
<Text>{title}</Text>
</Button>
);
});
export default function App() {
//UseCallback create point for put in button props, not function but point. Without useCallback component will rerender every time.
const handlePressClearDecline = useCallback(() => {}, []);
const handlePressExtraDecline = useCallback((socketId) => {
console.log(socketId);
}, []);
return (
<>
<ClearCustomButtom
title="ClearCustomButton"
onPress={handlePressClearDecline}
/>
<ExtraCustomButtom
title="ExtraCustomButton"
onPress={handlePressExtraDecline}
socketId="777"
/>
<RequestCustomButtom title="Send request" />
</>
);
}
This example of most common case in react-native, how to write pressable component.
I'm trying to write a test using jest for my login component. How can I test that the handleLogin function is called on clicking the 'log in' button in the form, when the handLogin function is not passed to the Login component as a prop?
This is the code I currently have but I'm unsure how to assign the mockHandler to the handleLogin function.
Login.js :
const Login = () => {
const [userName, setUserName] = useState('')
const [password, setPassword] = useState('')
const handleLogin = (event) => {
event.preventDefault()
try {
loginService.login({userName, password})
setUserName('')
setPassword('')
} catch (exception) {
// ...
}
}
return (
<div>
Log in
<form onSubmit = {handleLogin}>
<div>
User name
<input
type="text"
value={userName}
autoComplete="on"
onChange = {({target}) => setUserName(target.value)}
/>
</div>
<div>
Password
<input
type="password"
value={password}
autoComplete="on"
onChange = {({target}) => setPassword(target.value)}
/>
</div>
<button type="submit">log in</button>
<div>
forgot password
</div>
</form>
</div>
)
}
export default Login
Login.test.js :
import React from 'react'
import '#testing-library/jest-dom/extend-expect'
import { render, screen } from '#testing-library/react'
import userEvent from '#testing-library/user-event'
import Login from './Login'
test('clicking button calls event handler once', () => {
const mockHandler = jest.fn()
render(
<Login handleLogin={mockHandler}/>
)
const user = userEvent.setup()
const button = screen.getByText('log in')
user.click(button)
expect(mockHandler.mock.calls).toHaveLength(1)
})
But as the handleLogin is not passed as props this doesn't work.
Thanks.
For testing that a function was called by clicking a button
describe('button field element methods', () => {
it('should trigger onClick function', async () => {
await act(async () => {
const onClick = jest.fn();//this is your mock function
const { container } = render(<Button onClick={onClick} />);
const vdsTag = container.querySelector('button');
fireEvent.click(vdsTag); // this is how you programtically click a button
expect(onClick).toBeCalled(); // this is how you expect a function to be called
});
});
});
So maybe change your code to:
// import service mock function here
// e.g. import {loginService} from './service';
const loginSpy = jest.spyOn(loginService , 'login');
const customTag=render(<Login />)
const button = customTag.querySelector('button')
fireEvent.click(button);
expect(loginSpy).toBeCalled();
// You can also inject user name and pass, and make sure the spy
is called with these values
I am not having this warning probably on the normal functioning of the app but I am having this problem in the unit tests. So I am doing a unit test for a Tabs component and it gives me the following warning:
my jsx file looks like this:
class SimpleTabs extends React.Component {
handleChange = (event, value) => {
const { onChange } = this.props;
onChange(value);
};
render() {
const { classes, selectedChannelIndex } = this.props;
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs
key={selectedChannelIndex}
value={selectedChannelIndex}
onChange={this.handleChange}
classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}
>
{CHANNELS_ARRAY &&
CHANNELS_ARRAY.map((channel, i) => (
<Tab
key={i}
value={i}
label={channel.channel}
classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
/>
))}
</Tabs>
</AppBar>
<TabContainer>{this.props.children}</TabContainer>
</div>
);
}
}
export default withStyles(styles)(SimpleTabs);
and my unit test file looks like this:
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { configure, mount } from 'enzyme';
import { shallowToJson } from 'enzyme-to-json';
import Tabs from '../../../src/components/common/Tabs/Tabs';
configure({ adapter: new Adapter() });
const defaultProps = { selectedChannelIndex: 0, value: 0, selectInput: 0 };
const setup = (props = {}) => {
const setupProps = { ...defaultProps, ...props };
return shallow(<Tabs {...setupProps} />);
};
describe('Tabs', () => {
const wrapper = mount(<Tabs {...defaultProps} />);
it('should be defined', () => {
expect(Tabs).toBeDefined();
});
it('should render correctly', () => {
const tree = mount(<Tabs />);
expect(shallowToJson(tree)).toMatchSnapshot();
});
});
I've seen others asking about this warning as well but many times it's said that to add the value on the tabs element is the solution but is not working for me.
I am use bootstrap modal in reactjs project. Here is the link of package which i have installed in my project: https://www.npmjs.com/package/react-responsive-modal
When, I click on open the modal button then it is working, but when i click on close button then close button is not working. I am using the hooks in my project. Below, I have mentioned my code:
import React, { useState } from 'react'
import Modal from 'react-responsive-modal'
const Login = () => {
const [open, openModal] = useState(false)
const onOpenModal = () => {
openModal({open: true})
};
const onCloseModal = () => {
openModal({open: false})
};
return(
<div>
<h1>Login Form</h1>
<button onClick={onOpenModal}>Open modal</button>
<Modal open={open} onClose={onCloseModal} center>
<h2>Simple centered modal</h2>
</Modal>
</div>
)
}
export default Login;
The issue is because, you are setting object in state,
openModal({open: true})
This will store object in state.
setState require's direct value which needs to be change, your setState must be this,
const onOpenModal = () => {
openModal(!open) //This will negate the previous state
};
const onCloseModal = () => {
openModal(!open) //This will negate the previous state
};
Demo
You can simplify your code and just use 1 change handle for your modal,
const Login = () => {
const [open, openModal] = useState(false)
const toggleModal = () => {
openModal(!open)
};
return(
<div>
<h1>Login Form</h1>
<button onClick={toggleModal}>Open modal</button>
<Modal open={open} onClose={toggleModal} center>
<h2>Simple centered modal</h2>
</Modal>
</div>
)
}
Demo
Your naming of the model hook is misleading and you're using the setState part of the Hook wrong, probably mixing it up with the this.setState convention for non-Hook React code.
import React, { useState } from 'react'
import Modal from 'react-responsive-modal'
const Login = () => {
const [modalOpen, setModalOpen] = useState(false)
const onOpenModal = () => {
setModalOpen(true)
};
const onCloseModal = () => {
setModalOpen(false)
};
return(
<div>
<h1>Login Form</h1>
<button onClick={onOpenModal}>Open modal</button>
<Modal open={modalOpen} onClose={onCloseModal} center>
<h2>Simple centered modal</h2>
</Modal>
</div>
)
}
export default Login;
How would i be able to unit-test onChange method on this component.
Comment.js
import React from "react";
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
const Comment = (props) => (
<div>
<form onSubmit={props.onSubmit}>
<TextField
type="text"
id="outlined-multiline-static"
label="Write A Comment"
multiline
name="comment_body"
value={props.commentBody}
rows="10"
fullWidth
margin="normal"
variant="outlined"
onChange={props.commentChange}
/>
{/* <Button type="submit" variant="outlined" component="span" color="primary">
Post A Comment
</Button> */}
<button type="submit" variant="outlined" component="span" color="primary">
Write a Comment
</button>
</form>
</div>
)
export default Comment;
This is my attempt to unit test the onChange component, getting a
Method “simulate” is meant to be run on 1 node. 0 found instead
around this line
const component = shallow(<Comment commentChange={onChangeMock} commentBody={'test'} />)
component.find('input').simulate('change');
Comment.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';
import Comment from './Comment';
describe('Should render <Comment/> component', () => {
it('Should render form', () => {
const wrapper = shallow(<Comment/>)
// wrapper.find('Form').at(0)
expect(wrapper.find("form")).toHaveLength(1); // checks if there is a form.
})
it('Should render button', () => {
const wrapper = shallow(<Comment/>)
expect(wrapper.find('button')).toHaveLength(1);
})
it('should check for onChange method', () => {
// const wrapper = shallow(<Comment onChange={}/>)
const onChangeMock = jest.fn();
// const event = {
// preventDefualt(){},
// target: {
// value: 'testing'
// }
// }
const component = shallow(<Comment commentChange={onChangeMock} commentBody={'test'} />)
component.find('input').simulate('change');
expect(onChangeMock).toBeCalledWith('test')
})
})
The Comment component is being passed in another component like this:
ImageContainer.js
state = {
isComment: false,
comment_body: ""
}
handleCommentChange = (e) => {
this.setState({
comment_body: e.target.value
})
}
commentSubmit = (event, id) => {
event.preventDefault();
console.log(this.state.comment_body); // doesn't get console.log
// note that commentBody is being used for the req.body as well so its called by req.body.commentBody
const commentBody = this.state.comment_body
const data = {
commentBody,
id
}
this.props.postComment(data);
this.setState({
comment_body: ''
})
}
<Comment onSubmit={(e) => this.commentSubmit(e, img.id)}
commentBody={this.state.comment_body }
commentChange={this.handleCommentChange}/>
The reason you are having the error is because when you call component.find('input') it returns an array of matched components, so what you want to do is
component.find('input').at(0).simulate('change')
However, there is another way you can test this, which is my preferred method.
component.find('input').at(0).props().onChange()
Below is the correct way to do the test with both methods
import React from "react";
import Enzyme, { shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Comment from "./Comment";
import TextField from "#material-ui/core/TextField";
Enzyme.configure({ adapter: new Adapter() });
describe("Should render <Comment/> component", () => {
it("should check for onChange method (1)", () => {
// const wrapper = shallow(<Comment onChange={}/>)
const onChangeMock = jest.fn();
const component = shallow(
<Comment commentChange={onChangeMock} commentBody={"test"} />
);
component
.find(TextField)
.at(0)
.simulate("change", "test");
expect(onChangeMock).toBeCalledWith("test");
});
it("should check for onChange method (2)", () => {
// const wrapper = shallow(<Comment onChange={}/>)
const onChangeMock = jest.fn();
const component = shallow(
<Comment commentChange={onChangeMock} commentBody={"test"} />
);
component
.find(TextField)
.at(0)
.props()
.onChange();
expect(onChangeMock).toBeCalled();
});
});
For this particular test it will be better if you just use toBeCalled rather than toBeCalledWith. There is no need to test the value it is called with.