Given the following function:
./http.js
const http = {
refetch() {
return (component) => component;
}
}
I would like to mock the function in a test as follows:
./__tests__/someTest.js
import { refetch } from './http';
jest.mock('./http', () => {
return {
refetch: jest.fn();
}
}
refetch.mockImplementation((component) => {
// doing some stuff
})
But I'm receiving the error
TypeError: _http.refetch.mockImplementation is not a function
How can I mock the refetch function in the given example?
update:
When I modify the mock function slightly to:
jest.mock(
'../http',
() => ({ refetch: jest.fn() }),
);
I get a different error:
TypeError: (0 , _http.refetch)(...) is not a function
My guess it's something with the syntax where the curried function (or HOC function) is not mapped properly. But I don't know how to solve it.
Some of the real code I'm trying to test.
Note: The example is a bit sloppy. It works in the application. The example given is to give an idea of the workings.
./SettingsContainer
// ...some code
return (
<FormComponent
settingsFetch={settingsFetch}
settingsPutResponse={settingsPutResponse}
/>
);
}
const ConnectedSettingsContainer = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'https://some-url.com/api/v1/f',
},
settingsPut: (data) => ({
settingsPutResponse: {
url: 'https://some-url.com/api/v1/p',
}
}),
}),
)(SettingsContainer);
export default ConnectedSettingsContainer;
Then in my component I am getting the settingsPutResponse via the props which react-refetch does.
I want to test if the user can re-submit a form after the server has responded once or twice with a 500 until a 204 is given back.
./FormComponent
// ...code
const FormComp = ({ settingsResponse }) => {
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsResponse && settingsResponse.fulfilled) {
setSuccess(true);
}
}, [settingsResponse]);
if (success) {
// state of the form wil be reset
}
return (
<form>
<label htmlFor"username">
<input type="text" id="username" />
<button type="submit">Save</button>
</form>
)
};
The first question to ask yourself about mocking is "do I really need to mock this?" The most straightforward solution here is to test "component" directly instead of trying to fake out an http HOC wrapper around it.
I generally avoid trying to unit test things related to I/O. Those things are best handled with functional or integration tests. You can accomplish that by making sure that, given same props, component always renders the same output. Then, it becomes trivial to unit test component with no mocks required.
Then use functional and/or integration tests to ensure that the actual http I/O happens correctly
To more directly answer you question though, jest.fn is not a component, but React is expecting one. If you want the mock to work, you must give it a real component.
Your sample code here doesn't make sense because every part of your example is fake code. Which real code are you trying to test? I've seen gigantic test files that never actually exercize any real code - they were just testing an elaborate system of mocks. Be careful not to fall into that trap.
I'm trying to run a unit test in which I make an axios call and this returns an error. I have managed to mock the call successfully but on error I call an dependency on an external library (vue-toasted) to display an error message.
When my unit test hits toasted it is 'undefined':
TypeError: Cannot read property 'error' of undefined
this.$toasted.error('Search ' + this.searchString + ' returned no results', etc.....
I have attempted to register this dependancy in my test wrapper as such
import Toasted from 'vue-toasted';
jest.mock(Toasted);
these don't seem to provide the wrapper with the correct dependency. Here is my test:
it('searches users via axios and returns 404', async () => {
mock.onPost(process.env.VUE_APP_IDENTITY_API_URL + 'user/search').reply(404);
await wrapper.vm.searchUsers(1);
expect(mock.history.post.length).toBe(1);
expect(wrapper.vm.users).toBeNull();
expect(wrapper.vm.pagingInfo).toBeNull();
})
this is how my component wrapper is mocked up:
const wrapper = shallowMount(Users, {
stubs: {
RouterLink: RouterLinkStub
}
});
does anyone know what the correct syntax would be to register this syntax?
Try add it:
const wrapper = shallowMount(Users, {
mocks: {
$toasted: {
error: () => {},
}
}
});
Of course you can add more functionality to your mocks.
In one of my components I have the following functions:
addNewIndicator(attrs = {}) {
const value = attrs.value || 'Indicator'
const type = attrs.type || 'Generic Type'
this.createIndicator(value).then(
console.log('Indicator Created.')
)
}
async createIndicator(value) {
await this.props.createIndicatorMutation({
variables: {
value
},
update: (store, { data: { indicator }} ) => {
const data = store.readQuery({ query: INDICATOR_FEED_QUERY })
data.indicatorFeed.splice(0, 0, indicator)
store.writeQuery({
query: INDICATOR_FEED_QUERY,
data,
})
}
})
}
addNewIndicator() is triggered on a button click. When it runs, I get the following error:
TypeError: this.createIndicator is not a function
It is pointing to this line:
this.createIndicator(value).then(
I've done quite a bit of Googling, but haven't been able to figure out why this is the case. My understanding is that async functions can be called like that, but perhaps I'm missing something. Sorry if this is a silly question, I'm still learning React!
Also, I created the project using create-react-app and haven't modified it much other than adding some packages. Thanks for any help!
Edit to add how it is called. It is called from a child component props:
<Button primary onClick={this.handleAddSelectionClick}>Add Selected As Indicator</Button>
and handleAddSelectionClick:
handleAddSelectionClick = () => {
...snip...
this.props.addNewIndicator({
value: new_indicator_str,
})
}
try to validate that your 2 methods using the same context (this) -
you might need to do something like that in the constructor:
this.addNewIndicator = this.addNewIndicator.bind(this);
this.createIndicator = this.createIndicator.bind(this);
You probably just forgot to bind “this”. As you passed addNewIndicator as a callback, it lost its context. In this article several methods of binding callbacks are described, with all pros and cons of each.
https://reactjs.org/docs/handling-events.html
I want to globally catch errors in my React application.
But every time the error is caught/forwarded twice to my registered function.
Example code:
window.onerror = (msg, url, lineNo, columnNo, error) => {
console.log(msg)
alert(msg)
}
class TodoApp extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<button onClick={(e)=>{
console.log("clicked")
null.bla
}}>
Create an error
</button>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
Here is a JS-fiddle:
https://jsfiddle.net/dmxur0rc/4/
The console only shows one 'clicked' log, so it's not the button that fires twice, but the error event.
It is known react error, related with implementation of error boundaries.
I found a basic solution to this that should work in all scenarios.
It turns out that the object is identical in all calls, you could set up something to match them exactly, or you could just attach a custom attribute to the error object...
Admittedly this may only work with window.addEventListener('error', function...), as you are given the genuine error object as an argument, as opposed to window.onerror = function... which gets the data parts, such as message & lineNumber as opposed to the real error.
This is basically how I'm using it:
window.addEventListener('error', function (event) {
if (event.error.hasBeenCaught !== undefined){
return false
}
event.error.hasBeenCaught = true
// ... your useful code here
})
If this is called with the same error twice it will exit before getting to your useful code, only executing the useful code once per error.
You need to return true from your error handler otherwise the default error handler will fire:
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror
When the function returns true, this prevents the firing of the default event handler.
Also note that other error handlers may be in place via addEventHandler.
As mentioned in other answers, the problem is in React in DEV mode. In this mode it re-throws all exceptions to "improve debugging experience".
I see 4 different error scenarios
Normal JS errors (for example, from an event handler, like in the question).
These are sent to window.onerror twice by React's invokeGuardedCallbackDev.
JS errors that happen during render, and there is no React's error boundary in the components tree.
The same as scenario 1.
JS errors that happen during render, and there is an error boundary somewhere in the components tree.
These are sent to window.onerror once by invokeGuardedCallbackDev, but are also caught by the error boundary's componentDidCatch.
JS errors inside promises, that were not handled.
These aren't sent to window.onerror, but rather to window.onunhandledrejection. And that happens only once, so no problem with this.
My workaround
window.addEventListener('error', function (event) {
const { error } = event;
// Skip the first error, it is always irrelevant in the DEV mode.
if (error.stack?.indexOf('invokeGuardedCallbackDev') >= 0 && !error.alreadySeen) {
error.alreadySeen = true;
event.preventDefault();
return;
}
// Normal error handling.
}, { capture: true });
I use this error boundary to handle both React and global errors. Here is some advice from the React documentation:
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.
A class component becomes an error boundary if it defines either (or
both) of the lifecycle methods static getDerivedStateFromError() or
componentDidCatch().
Only use error boundaries for recovering from unexpected exceptions;
don’t try to use them for control flow.
Note that error boundaries only catch errors in the components below them in the tree; An error boundary can’t catch an error within itself.
class ErrorBoundary extends React.Component {
state = {
error: null,
};
lastError = null;
// This lifecycle is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as a parameter and should return a value to update state.
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
error,
};
}
componentDidMount() {
window.onerror = (msg, url, line, column, error) => {
this.logError({
error,
});
};
}
// getDerivedStateFromError() is called during the “render” phase, so side-effects are not permitted. For those use cases, use componentDidCatch() instead.
// This lifecycle is invoked after an error has been thrown by a descendant component. It receives two parameters:
// error - The error that was thrown.
// info - An object with a componentStack key containing
componentDidCatch(error, info) {
// avoid calling log error twice
if (this.lastError && this.lastError.message === this.state.error.message) {
return true;
}
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
// logComponentStackToMyService(info.componentStack);
this.logError({
error,
info,
});
}
async logError({
error,
info
}) {
this.lastError = error;
try {
await fetch('/error', {
method: 'post',
body: JSON.stringify(error),
});
} catch (e) {}
}
render() {
if (this.state.error) {
return display error ;
}
return this.props.children;
}
}
Another way is to store the last error's message in state and check when it happens for the second time.
export default MyComponent extends React.Component{
constructor(props){
super(props);
this.state = {message: null};
}
componentDidMount(){
const root = this;
window.onerror = function(msg, url, line, column, error){
if(root.state.message !== msg){
root.setState({message: msg});
// do rest of the logic
}
}
}
}
But anyways it is good idea to use React Error Boundaries. And you can
implement this global javascript error handling inside the error
boundary component. Where you can both catch js errors (with
window.onerror) and React errors (with componendDidCatch).
My workaround: Apply debouncing (in typescript):
import { Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
let errorObserver: any;
window.onerror = (msg, url, lineNo, columnNo, error) => {
if (!errorObserver) {
new Observable(observer => {
errorObserver = observer
}).pipe(debounceTime(300)) // wait 300ms after the last event before emitting last event
.pipe(distinctUntilChanged()) // only emit if value is different from previous value
.subscribe(handleOnError)
}
errorObserver.next(
{
msg,
url,
lineNo,
columnNo,
error
}
)
return true
}
const handleOnError = (value: any) => {
console.log('handleOnError', value)
}
This looks like it's probably firing twice due to the nature of running it on JSFiddle. In a normal build process (with webpack and babel) code with a script error like that should fail to transpile.
ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'title' of undefined
I used the same code from 'heroes' example to load a detail object in a single route. I kept getting this error because, I think, the data is not loaded before the view already started to render and that's why I am getting this error.
I had this problem to display "currentUser.name" which I solved by using currentUser?.name but in this case, it doesn't make sense to add to all the places of the object properties with '?'.
I have to spend more time in OnInit than what heroes example did. Because I need to fetch more stuff. So I know that the view just kicks in much earlier than the binding object journal is loaded.
ngOnInit() {
this.userService.getUser().then( (user) => {
this.currentUser = user;
this.accountsService.getAccounts().then( (accounts) => {
this.accounts = accounts;
this.userAccounts = this.currentUser.accounts.map(
accountId => this.accounts.find(
elem => elem.id == new String(accountId)
)
);
this.route.params
.switchMap((params: Params) => this.journalService.getJournal(+params['id']))
.subscribe( (journal) => {
console.log("journal", journal);
this.journal = journal;
});
});
});
}
How can I instruct the view to wait until the data is loaded before it starts to render itself?
Or is there something wrong with the code?
You could wrap your template with a condition.
Steps:
1 - Create a variable and initializes it with a falsy value:
loaded: boolean = false;
2 - Set it to true when your request is finished:
this.route.params
.switchMap((params: Params) => this.journalService.getJournal(+params['id']))
.subscribe((journal) => {
console.log("journal", journal);
this.journal = journal;
this.loaded = true; // -> here
});
3 - In your template use *ngIf to prevent errors:
<ng-container *ngIf="loaded">
... content
</ng-container>