i got an error with this message :
TypeError: this.unsubscribeFocus is not a function Jest
what i want to test is this code
componentWillUnmount() {
// Remove the event listener
this.unsubscribeFocus();
this.unsubscribeBlur();
}
then on file test.js
test('componentWillUnmount as expected', async () => {
class Foo extends React.Component {
// constructor(props) {
// this.unsubscribeFocus = jest.fn();
// this.unsubscribeFocus = jest.fn();
// }
unsubscribeFocus = () => {};
unsubscribeBlur = () => {};
render() {
return (<ReservationNotFound />)
}
}
wrapper.unmount();
const componentWillUnmount = wrapper.instance().componentWillUnmount();
expect(componentWillUnmount).not.toEqual(null);
});
});
but at the wrapper.unmount i got an error that like this
so how to solve this unsubscribeFocus & unsubscribeBlur with jest when it will unmount?
Can anyone help me understand why the handler function in the HOC does not get called when the blur event is simulated in this jest test?
//MyInput.js
export const MyInput = (props) => (
<div>
...
<input onBlur={ props.onBlur } />
...
</div>
)
//HOC.js
export const withHOC = (SomeComponent) => {
return (props) => {
const handleBlur = () => {
console.log('this does not get called');
//do stuff
props.onBlur();
}
return <SomeComponent onBlur={handleBlur} />;
}
}
// test.js
describe('HOC', () => {
it('fires a callback in response to the blur event', () => {
const ModifiedInput = withHOC(MyInput);
const onBlurMock = jest.fn(() => {
console.log('this does get called');
});
const wrapper = mount(<ModifiedInput onBlur={onBlurMock} />);
const input = wrapper.find('input');
input.simulate('blur');
expect(onBlurMock).toHaveBeenCalled();
})
})
In my test file I am mounting a Component and one of the nested Components is making me troubles. This is the Component:
class CacheHandler extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
isLatestVersion: false,
refreshCacheAndReload: () => {
if (caches) {
caches.keys().then((names) => {
names.forEach((name) => {
caches.delete(name);
})
});
}
window.location.reload(true);
}
};
// ...some other code
}
render() {
const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
return this.props.children({ loading, isLatestVersion, refreshCacheAndReload });
}
}
CacheHandler.propTypes = {
children: PropTypes.func.isRequired
};
export default CacheHandler;
I do not know how properly mock the constructor's refreshCacheAndReload property that gives me grey hair. It would be totally ok if it just does not do anything in the mock, but it shoud be found during the mounting process. At the moment, when I run my test, I get because of that part ReferenceError: EventSource is not defined
This is what I tried inside of my test but failed (Error: "CacheHandler" is read-only.):
const fakeCacheHandler = jest.fn(() => ({
constructor(props) {
//super(props);
this.state = {
loading: false,
isLatestVersion: false,
refreshCacheAndReload: () => { }
}},
render() {
const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
return this.props.children({ loading, isLatestVersion, refreshCacheAndReload });
}
}))
CacheHandler = fakeCacheHandler;
I also tried to define the property directly in test but without success:
Object.defineProperty(CacheHandler, 'CacheHandler', {
value: jest.fn().mockImplementation(query => ({
loading: false,
isLatestVersion: false,
refreshCacheAndReload: () => {}
}))
})
I also tried to mock the whole module in the test like this:
jest.mock('../../components/utilities/CacheHandler', function() {
return jest.fn().mockImplementation(() => {
return {
refreshCacheAndReload: () => {},
render: () => {this.props.children({ loading:false, isLatestVersion:false, refreshCacheAndReload })},
}})
});
but still not successful.
jest.spyOn fails as well(Cannot spy the refreshCacheAndReload property because it is not a function; undefined given instead)
const fakeHandler = new CacheHandler();
const methodSpy = jest.spyOn(fakeHandler, "refreshCacheAndReload");
methodSpy.mockImplementation(() => {
console.log('test!');
})
This is how the test itself look like now:
it('renders MembersList', async () => {
const Component = withMemory(AdminAppCore, ROUTE_ADMIN_MEMBERS);
const result = mount(
<MockProvider stores={{ memberStore, programStore }}>
<Component />
</MockProvider>
);
console.log(result.debug());
await waitForState(result, state => state.loading === false);
expect(result.find(MembersList).length).toBe(1);
result.unmount();
});
I tried to mock the constructor of child element inside the test like this, but if failed(TypeError: this.props.children is not a function):
it('renders MembersList', async () => {
const Component = withMemory(AdminAppCore, ROUTE_ADMIN_MEMBERS);
const mockrefreshCacheAndReload = jest.fn(() => ({}));
const component = shallow(<CacheHandler/>);
component.setState({refreshCacheAndReload: mockrefreshCacheAndReload});
const result = mount(
<MockProvider stores={{ memberStore, programStore }}>
<Component />
</MockProvider>
);
console.log(result.debug());
await waitForState(result, state => state.loading === false);
expect(result.find(MembersList).length).toBe(1);
result.unmount();
});
So I am mounting the AdminAppCore, and inside of the AdminAppCore is the nested component that causes troubles.
Can anyone please explain me how can I mock the refreshCacheAndReload state inside of the nested Component constructor?
As I cannot post this in the comments, so please try this.
const mockrefreshCacheAndReload = jest.fn(() => ({}));
it('tests the method', () => {
const component = shallow(<CacheHandler/>);
component.setState({refreshCacheAndReload: mockrefreshCacheAndReload})
})
I'm building a search field that is fetching from a data base upon users input and I'm struggling a bit. At the moment, it is fetching data in every keystroke, which is not ideal. I have looked at different answers and it seems that the best option is to do this in componentDidUpdate() and get a ref of the input feel to compare this with the current value through a setTimeout().
I have tried this, but I'm still fetching during every keystroke, not sure why? See a sample of the component below:
class ItemsHolder extends Component {
componentDidMount() {
//ensures the page is reloaded at the top when routing
window.scrollTo(0, 0);
this.props.onFetchItems(this.props.search);
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.search !== this.props.search) {
console.log(
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
);
setTimeout(() => {
console.log(
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
);
if (
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
) {
this.props.onFetchItems(this.props.search, this.props.category);
}
}, 500);
}
}
I'm using Redux for state management. Here is the function that is called when fetching items:
export const fetchItemsFromServer = (search) => {
return (dispatch) => {
dispatch(fetchItemsStart());
const query =
search.length === 0 ? '' : `?orderBy="country"&equalTo="${search}"`;
axios
.get('/items.json' + query)
.then((res) => {
const fetchedItems = [];
for (let item in res.data) {
fetchedItems.push({
...res.data[item],
id: item,
});
}
dispatch(fetchItemsSuccess(fetchedItems));
})
.catch((error) => {
dispatch(fetchItemsFail(error));
});
};
};
This is how I'm setting the ref in the search component:
class Search extends Component {
constructor(props) {
super(props);
this.searchInput = React.createRef();
}
componentDidMount() {
this.props.onSetRef(this.searchInput);
}
render() {
return (
<Input
ref={this.searchInput}
toolbar
elementType={this.props.inputC.elementType}
elementConfig={this.props.inputC.elementConfig}
value={this.props.inputC.value}
changed={(event) => this.props.onChangedHandler(event)}
/>
);
}
}
Based on a tutorial I found this should work. For your reference, see the code from this tutorial. I don't see why wouldn't the above work. The only difference is that the tutorial uses hooks.
const Search = React.memo(props => {
const { onLoadIngredients } = props;
const [enteredFilter, setEnteredFilter] = useState('');
const inputRef = useRef();
useEffect(() => {
const timer = setTimeout(() => {
if (enteredFilter === inputRef.current.value) {
const query =
enteredFilter.length === 0
? ''
: `?orderBy="title"&equalTo="${enteredFilter}"`;
fetch(
'https://react-hooks-update.firebaseio.com/ingredients.json' + query
)
.then(response => response.json())
.then(responseData => {
const loadedIngredients = [];
for (const key in responseData) {
loadedIngredients.push({
id: key,
title: responseData[key].title,
amount: responseData[key].amount
});
}
onLoadIngredients(loadedIngredients);
});
}
}, 500);
return () => {
clearTimeout(timer);
};
}, [enteredFilter, onLoadIngredients, inputRef]);
Following recommendation to debounceInput:
import React, { Component } from 'react';
// import classes from './Search.css';
import Input from '../../UI/Input/Input';
// redux
import * as actions from '../../../store/actions/index';
import { connect } from 'react-redux';
class Search extends Component {
componentDidUpdate(prevProps, prevState) {
if (prevProps.search !== this.props.search) {
this.props.onFetchItems(this.props.search, this.props.category);
}
}
debounceInput = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
};
};
render() {
return (
<Input
toolbar
elementType={this.props.inputC.elementType}
elementConfig={this.props.inputC.elementConfig}
value={this.props.inputC.value}
changed={(event) =>
this.debounceInput(this.props.onChangedHandler(event), 500)
}
/>
);
}
}
const mapStateToProps = (state) => {
return {
inputC: state.filtersR.inputConfig,
search: state.filtersR.search,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onChangedHandler: (event) => dispatch(actions.inputHandler(event)),
onFetchItems: (search, category) =>
dispatch(actions.fetchItemsFromServer(search, category)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
Here is the final solution after help here:
import React, { Component } from 'react';
// import classes from './Search.css';
import Input from '../../UI/Input/Input';
// redux
import * as actions from '../../../store/actions/index';
import { connect } from 'react-redux';
const debounceInput = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
};
};
class Search extends Component {
componentDidUpdate(prevProps, _prevState) {
if (prevProps.search !== this.props.search) {
this.responseHandler();
}
}
responseHandler = debounceInput(() => {
this.props.onFetchItems(this.props.search, this.props.category);
}, 1000);
render() {
return (
<Input
toolbar
elementType={this.props.inputC.elementType}
elementConfig={this.props.inputC.elementConfig}
value={this.props.inputC.value}
changed={(event) => this.props.onChangedHandler(event)}
/>
);
}
}
const mapStateToProps = (state) => {
return {
inputC: state.filtersR.inputConfig,
search: state.filtersR.search,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onChangedHandler: (event) => dispatch(actions.inputHandler(event)),
onFetchItems: (search, category) =>
dispatch(actions.fetchItemsFromServer(search, category)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
You really just need to debounce your input's onChange handler, or better, the function that is actually doing the asynchronous work.
Very simple debouncing higher order function:
const debounce = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
}
};
Example Use:
fetchData = debounce(() => fetch(.....).then(....), 500);
componentDidUpdate(.......) {
// input value different, call fetchData
}
<Input
toolbar
elementType={this.props.inputC.elementType}
elementConfig={this.props.inputC.elementConfig}
value={this.props.inputC.value}
changed={this.props.onChangedHandler}
/>
Demo Code
const debounce = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(fn, delay, [...args]);
};
};
const fetch = (url, options) => {
console.log("Fetching", url);
return new Promise((resolve) => {
setTimeout(() => {
console.log("Fetch Resolved");
resolve(`response - ${Math.floor(Math.random() * 1000)}`);
}, 2000);
});
};
export default class App extends Component {
state = {
search: "",
response: ""
};
changeHandler = (e) => {
const { value } = e.target;
console.log("search", value);
this.setState({ search: value });
};
fetchData = debounce(() => {
const { search } = this.state;
const query = search.length ? `?orderBy="country"&equalTo="${search}"` : "";
fetch(
"https://react-hooks-update.firebaseio.com/ingredients.json" + query
).then((response) => this.setState({ response }));
}, 500);
componentDidUpdate(prevProps, prevState) {
if (prevState.search !== this.state.search) {
if (this.state.response) {
this.setState({ response: "" });
}
this.fetchData();
}
}
render() {
const { response, search } = this.state;
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<label>
Search
<input type="text" value={search} onChange={this.changeHandler} />
</label>
<div>Debounced Response: {response}</div>
</div>
);
}
}
I have this code:
const handleButton = React.useCallback(async () => {
try {
await client.clearStore();
navigation.push('screen');
} catch (e) {
logger.error('error', e);
}
}, []);
I'm testing the nagivation to screen, but with the await I can't execute or reach the navigation.push(). If I remove the await on client.clearStore it's all ok, but the behaviour could be not as expected (I need first to clearStore then to navigate).
Any idea?
Edit:
My test is like this one:
import { useApolloClient } from '#apollo/react-hooks';
// After imports
jest.mock('#apollo/react-hooks');
// In describe:
describe('text', () => {
beforeEach(async () => {
// #ts-ignore
moment.mockClear();
useApolloClient.mockClear();
});
// My test:
it.only('should redirects to MyComponent when button is pressed', async () => {
const apolloClient = {
clearStore: jest.fn()
};
useApolloClient.mockImplementation(() => apolloClient);
apolloClient.clearStore
.mockReturnValueOnce(
Promise.resolve('foo')
);
moment.mockImplementation(
(args: moment.MomentInput) =>
jest.requireActual('moment')(args || '2020-07-30T20:00:00.000Z')
);
const navigation = {
push: jest.fn()
};
const rendered = render(
<MyComponent
paymentGatewayCode='khipu'
allowToPay={true}
expiresDate={'2020-07-30T20:00:00.000Z'}
navigation={navigation}
/>
);
const button = rendered.getByTestId('button');
fireEvent.press(button);
console.log('navigation.push.mock.calls', navigation.push.mock.calls);
expect(navigation.push.mock.calls.length).toBe(1);
expect(navigation.push.mock.calls[0]).toEqual(['MyComponent']);
});
And I get this while running the test: