react state is not updating in jest - javascript

In my react component I have two functions. handleChangeInput(e) is called on 'OnChange' of input field and checkFields() is called from handleChangeInput(e)
constructor(){
super()
this.state={
email: '',
password:'',
validFields: false
}
}
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value},()=>{
this.checkFields();
});
}
checkFields(){
if (this.state.email.length>0 && this.state.password.length>0 ) {
this.setState({validFields: true});
}else {
this.setState({validFields: false});
}
}
And in my index.test.js I have
describe('<Login />', () => {
describe('handleChangeInput', () => {
const component = new Login()
const wrapper = shallow(<Login />);
beforeEach(() => {
component.setState = jest.fn()
})
test('calls setState validFields false when no email/password', () => {
const state = { state : { email: '', password: ''} }
const args = { target : { name: 'name', value: 'value' } }
component.handleChangeInput.call(state, args)
expect(component.setState.mock.calls.length).toBe(1)
expect(wrapper.state().validFields).toEqual(false)
})
test('calls setState validFields true when email/password are ok', () => {
const state = { state : { email: 'email', password: 'password' } }
const args = { target : { name: 'name', value: 'value' } }
component.handleChangeInput.call(state, args)
expect(component.setState.mock.calls.length).toBe(1)
expect(wrapper.state().validFields).toEqual(false)
})
})
});
But my state is not being updated. As a result, 'validFields' is not set to true and my second test is failing. I tried wrapper.update() and wrapper.instance().forceUpdate() but still no success. Any help would be appreciated

I am guessing it might be because you override the setState function with jest.fn()
component.setState = jest.fn()
})
how about removing this?

hope my answer does not come too late, but you are trying to update the state in a wrong way.
First of all, remove these two:
const component = new Login()
beforeEach(() => {
component.setState = jest.fn()
})
And most likely you want to change this:
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value},()=>{
this.checkFields();
});
}
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState(()=>{
return { email: name}
});
this.setState(()=>{
return { password: value }
});
this.checkFields();
}
const component = new Login() does not bring any value to this test and you should not mock the setState if you want that it's actually changed.
Instead you should test the actual component (like you partially already do here)
Change the code like this:
test('calls setState validFields true when email/password are ok', () => {
const args = { target : { email: 'email', password: 'password' } }
wrapper.instance().handleChangeInput(args)
expect(wrapper.state('email')).toEqual('email')
expect(wrapper.state('password')).toEqual('password')
expect(wrapper.state('validFields')).toBeTruthy()
})

I found this answer in one of the git forums. It worked for me.
// somewhere in your test setup code
global.flushPromises = () => {
return new Promise(resolve => setImmediate(resolve))
}
test('something with unreachable promises', () => {
expect.hasAssertions()
const component = mount(<Something />)
// do something to your component here that waits for a promise to return
return flushPromises().then(() => {
component.update() // still may be needed depending on your implementation
expect(component.html()).toMatchSnapshot()
})
})

Related

MockReturnValue is returning undefined on axios wrapper function

I am trying to test my componentDidMount and ensure that this axiosRequest returns an array. However no matter how I mock my axiosRequest function, it always returns undefined. What am I missing??
My axiosRequest wrapper fn:
export const axiosRequest = (type, url, body, headers) => {
return axios[type](url,
{
...body,
sharedLinkToken: store.getState()?.token ? store.getState()?.token : null,
activeWorkspace: body?.activeWorkspace ? body?.activeWorkspace :
store.getState()?.auth?.org?.activeWorkspace,
},
{ ...headers },
).then((res) => res?.data);
};
Calling said function in my CDM
async componentDidMount() {
try {
const fieldTypes = await axiosRequest('post', '/api/custom-fields/types');
console.log('fieldTypes: ', fieldTypes);
this.setState({ fieldTypes });
} catch (e) {
this.setState({ disable: true });
}
}
My test with its imports with my various ways to mock this function:
import * as axiosRequest from '../../../../utilities/utilities';
let mockAxiosRequest;
// jest.mock('axios');
// jest.mock('../../../../utilities/utilities', () => ({
// axiosRequest: jest.fn(),
// }));
beforeEach(() => {
props = {};
wrapper = shallow(<CreateCustomField {...props} />);
instance = wrapper.instance();
mockAxiosRequest = jest.spyOn(axiosRequest, 'axiosRequest');
});
it('Should find the wrapper', async () => {
mockAxiosRequest.mockResolvedValue(['stuff']);
// mockAxiosRequest.mockResolvedValue(() => ['stuff']);
// mockAxiosRequest.mockResolvedValueOnce(['stuff']);
// mockAxiosRequest.mockReturnThis(['stuff']);
// mockAxiosRequest.mockImplementation(() => ['stuff']);
// mockAxiosRequest.mockImplementation(() => Promise.resolve(['stuff']));
expect(wrapper.find('.create-custom-fields-modal').length).toBe(1);
expect(instance.state.fieldTypes.length).toBe(1);
});
I found that my mocks were working fine, however the componentDidMount was the issue. So because of this, I am doing everything manually, and everything is working as expected.
beforeEach(() => {
props = {};
mockAxiosRequest = jest.spyOn(axiosRequest, 'axiosRequest');
wrapper = shallow(<CreateCustomField {...props} />, { disableLifecycleMethods: true });
instance = wrapper.instance();
instance.setState({ fieldTypeOptions: [{ persistent_id: '123',field_type: 'Date & Time' }] });
});
Then my two tests that are now passing:
it('Should find the wrapper', async () => {
expect(wrapper.find('.create-custom-fields-modal').length).toBe(1);
});
it('Should call componentDidMount', async () => {
mockAxiosRequest.mockResolvedValueOnce([
{ persistent_id: '456', field_type: 'Date & Time' },
{ persistent_id: '123', field_type: 'Number' },
]);
await instance.componentDidMount();
expect(instance.state.fieldTypeOptions.length).toBe(2);
});

why componentdidmount called two times

I have React Component in componentDidMount fetch data from the server. The issue is componentDidMount called twice also the API called twice. I have a view increment API like youtube video views increment twice in the database because of twice API calling.
class SingleVideoPlay extends React.Component {
constructor(props) {
super(props);
this.player = React.createRef();
}
state = {
autoPlay: true,
relatedVideos: [],
video: null,
user: null,
comments: [],
commentInput: {
value: '',
touch: false,
error: false
},
following: false,
tab: 'comments'
};
_Mounted = false;
componentDidMount() {
this._Mounted = true;
if (this._Mounted) {
const videoId = this.props.match.params.id;
this.getVideoDetails(videoId);
}
}
componentWillUnmount() {
this._Mounted = false;
try {
clearInterval(this.state.videoInterval);
this.props.videoEditUrl('');
} catch (error) {}
}
captureVideoTime = async () => {
const { video } = this.state;
const result = await updateWatchTime({
id: video._id,
time: 1
});
if (result.status === 200) {
const updateVideo = {
...video,
secondsWatched: video.secondsWatched + 1
};
this.setState({ video: updateVideo });
}
};
videoEnded = () => {
clearInterval(this.state.videoInterval);
};
videoPause = () => {
clearInterval(this.state.videoInterval);
};
loadVideo = () => {
clearInterval(this.state.videoInterval);
};
playingVideo = () => {
const interval = setInterval(this.captureVideoTime, 1000);
this.setState({ videoInterval: interval });
};
getVideoDetails = async (videoId) => {
const video = await getVideo(videoId);
if (video.status === 200) {
let response = video.data;
if (this.props.userId)
if (response.user._id === this.props.userId._id)
this.props.videoEditUrl(`/video/edit/${response.media._id}`);
this.setState({
relatedVideos: response.videos.docs,
video: response.media,
user: response.user
});
this.checkIsFollowing();
this.updateVideoStat(response.media._id);
}
};
updateVideoStat = async (id) => videoView(id);
checkIsFollowing = async () => {
const { userId } = this.props;
const { video } = this.state;
if (userId && video) {
const response = await isFollow({
follower: userId._id,
following: video._id
});
if (response) {
this.setState({ following: response.following });
}
}
};
addOrRemoveFollowing = async () => {
this.checkIsFollowing();
const { following, video } = this.state;
const { userId } = this.props;
if (userId) {
if (following) {
const response = await removeFollow({
follower: userId._id,
following: video._id
});
this.setState({ following: false });
} else {
const response = await addFollow({
follower: userId._id,
following: video._id
});
this.setState({ following: true });
}
}
};
submitCommentHandler = async (event) => {
const { userId } = this.props;
event.preventDefault();
if (userId) {
const result = await saveComment({
mediaId: this.state.video._id,
parentId: '0',
userID: userId._id,
userName: userId.username,
comment: this.state.commentInput.value
});
console.log(result);
if (result.status === 200) {
this.getVideoComments();
this.setState({ commentInput: { value: '', touch: false, error: false } });
}
}
};
render() {
const { autoPlay, relatedVideos, video, user, comments, commentInput, following, tab } = this.state;
const { userId } = this.props;
return (
<div className="container-fluid">
some coponents
</div>
);
}
}
const mapStateToProps = (state) => ({
userId: state.auth.user
});
export default connect(mapStateToProps, { videoEditUrl })(SingleVideoPlay);
I don't know why componentDidMount called two times alse it shows memmory lecage issue.
How to Fix it.
Multiple componentDidMount calls may be caused by using <React.StrictMode> around your component. After removing it double calls are gone.
This is intended behavior to help detect unexpected side effects. You can read more about it in the docs. It happens only in development environment, while in production componentDidMount is called only once even with <React.StrictMode>.
This was tested with React 18.1.0
I think the issue exists on the parent component that used SingleVideoPlay component. Probably that parent component caused SingleVideoPlay component rendered more than once.
Also, there is an issue on your code.
componentDidMount() {
this._Mounted = true;
if (this._Mounted) {
const videoId = this.props.match.params.id;
this.getVideoDetails(videoId);
}
}
Here, no need to check if this._Mounted, because it will always be true.
1.Install jQuery by
npm i jquery
import $ from 'jquery'
create your function or jwuery code after the export command or put at the end of the file

storing array state objects in asyncStorage

I want to store an array state using async storage. but everytime i reload the app, it comes up blank. below is a sample code, and I have shown only the functions for better clarity.
componentDidMount() {
this.getDataSync();
}
getDataSync = async () => {
try {
const list = await AsyncStorage.getItem(LIST_STORAGE_KEY);
const parsedList = JSON.parse(list);
const obj = Object.keys(parsedList);
this.setState({ isDataReady: true, list: obj || [] });
} catch (e) {
Alert.alert('Failed to load list.');
}
}
handleAdd() {
const { firstname, lastname, email, phone} = this.state;
const ID = uuid();
const newItemObject = {
key: ID,
firstname: firstname,
lastname: lastname,
email: email,
phone: phone,
image: null,
};
this.setState(prevState => ({
list: [...prevState.list, newItemObject]
}));
this.saveItems(this.state.list);
}
saveItems = list => {
AsyncStorage.setItem(LIST_STORAGE_KEY, JSON.stringify(list));
};
You are not saving your list but getting keys from the list. const obj = Object.keys(parsedList); you are saving array indexes to state.
getDataSync = async () => {
try {
const list = await AsyncStorage.getItem(LIST_STORAGE_KEY);
const parsedList = JSON.parse(list);
this.setState({
isDataReady: true,
list: Array.isArray(parsedList) && parsedList.length && parsedList || []
});
} catch (e) {
Alert.alert('Failed to load list.');
}
}
Also pass saveItems as a callback to save the correct data.
this.setState(prevState => ({
list: [...prevState.list, newItemObject]
}), () => this.saveItems(this.state.list));
The .setState() method is may be asynchronous, so the result of setting the state, cannot be used immediately after setting it. If you want to use the results of setting the state, you should use the callback (2nd param), which is called after the state is actually set:
this.setState(
prevState => ({
list: [...prevState.list, newItemObject]
}),
() => this.saveItems(this.state.list)
);

How to test axios get request function in jest/enzyme tests?

I have follow React component which render as a child in parent component and props are passed:
<Component
localStorageValue={'test-walue'}
requestDataFunc={getData}
requestUserData={getUserData}
expectedResponseKey={'key'}
dataType={'test}
activePage={'index'}
saveData={this.setData}
/>
so requestDataFunc is a funtion which passed to component and runned in componentDidMount :
componentDidMount() {
requestDataFunc().then(({ data }) => {
const { selectedDataItems } = this.state;
const expectedResponseData = data[expectedResponseKey];
let interimDataArr = [];
expectedResponseData.forEach((item) => {
interimDataArr = [...interimDataArr, {
...item,
active: selectedDataItems.length ? selectedDataItems.some((selectedItemId) => selectedItemId === item.id) : false,
}];
});
}
but when I run my tests, I got:
TypeError: Cannot read property 'then' of undefined
requestDataFunc().then(({ data }) => {
const { selectedDataItems } = this.state;
const expectedResponseData = data[expectedResponseKey];
let interimDataArr = [];
I just starting to test render component:
describe('correct component render', () => {
const defaultProps = {
localStorageValue: 'test-walue',
requestDataFunc: jest.fn(),
requestUserData: jest.fn(),
expectedResponseKey: 'key',
dataType: 'test',
activePage: 'index',
saveData: jest.fn(),
};
const wrapper = shallow(<ComponentName { ...defaultProps } />);
test("render component", () => {
expect(wrapper.length).toEqual(1);
});
});
I suppose that I need to mock somehow request and data that this request should receive. How to do this correctly?
Have you tried mocking promise as below:
var mockPromise = new Promise((resolve, reject) => {
resolve(<mock response similar to actual promise response>);
});
describe('correct component render', () => {
const defaultProps = {
localStorageValue: 'test-walue',
requestDataFunc: jest.fn().mockReturnValueOnce(mockPromise),
requestUserData: jest.fn(),
expectedResponseKey: 'key',
dataType: 'test',
activePage: 'index',
saveData: jest.fn(),
};
const wrapper = shallow(<ComponentName { ...defaultProps } />);
test("render component", () => {
expect(wrapper.length).toEqual(1);
});
});
Axios get method returns a promise, so you when you mock that method you also need to return a Promise
jest.fn(() => Promise.resolve({}))
More on Jest async mocking
Relevant answer from SO

TypeError: dispatch is not a function when I try to test a method using JEST

I have a method that receives some values as a parameter and then dispatches an action. The problem is that when I shallow my component to test this method, I have an error saying that dispatch is not a function.
TEST:
test('it changes the state when submit is clicked', () => {
const wrapper = shallow(<WizardForm store={store}/>);
const values = {
entrySign: 'archivoSign',
signCertificateFile: 'file',
signCertificate: 'text',
entryAuth: 'notArchivoAuth',
authCertificateFile: 'file',
authCertificate: 'text'
}
const form = wrapper.instance();
//in this method I get the error
form.submit(values)
METHOD:
submit(values) {
var authCertificate = this.checkAuth(values);
var signCertificate = this.checkSign(values);
let req = {
authCertificate: authCertificate,
signCertificate: signCertificate,
userId: this.state.userId
}
const { dispatch } = this.props
dispatch({type: 'CERTIFICATES_FETCH_REQUESTED', payload: {req}})
}
Can anyone help me? I do not know what I am doing wrong.
Thanks in advance!
Okay so now I have this test:
it('works', () => {
const values = {
username: 'marc',
name: 'marc',
email: 'marc#hotmail.com',
entrySign: 'archivoSign',
signCertificateFile: 'file',
signCertificate: 'text',
entryAuth: 'notArchivoAuth',
authCertificateFile: 'file',
authCertificate: 'text'
}
const mapDispatchToProps = (dispatch) => ({
submit
});
const mockedStore = createMockStore();
const WizardFormWrapper = connect(reduxFormReducer, mapDispatchToProps)(WizardForm);
const wrapper = shallowWithStore(<WizardFormWrapper />, mockedStore);
wrapper.props().submit();
});
})
The problem now I get is: ReferenceError: submit is not defined
Any suggestions #RIYAJ KHAN ?

Categories

Resources