Jest mock a function within object - reactjs

Trying the following :
// Function Spies
const onSubmitSpy = jest.fn().mockName('onSubmitSpy');
const onHistoryPushSpy = jest.fn().mockName('onPushSpy');
// Default Props
const defaultProps = {
signupUserMutation: onSubmitSpy,
history: {
push: onHistoryPushSpy
}
};
Then within my test this spy gets called withing my code like this
history.push('/');
(I verified with mock Response) but the call count in test response always remains 0.
I have a feeling this is maybe because this is a nested object I am missing something ?
test('should submit and call routing change', () => {
...
expect(onHistoryPushSpy).toHaveBeenCalledTimes(1);
});
And I always get this error
Expected mock function to have been called one time, but it was called zero times.

Related

How to partially mock a custom react hook with Jest?

I would like to mock some of my custom React hook return value with Jest.
Here is an example of what i'm trying to do :
export default function useSongPlayer() {
const playSongA = () => {...}
const playSongB = () => {...}
const playSongC = () => {...}
return {
playSongA,
playSongB,
playSongC,
}
}
Now in my test, I would like to mock only playSongA and keep the other functions real implementations. I tried several approachs, including someting with jest.requireActual('hooks/useSongPlayer') but it seems that playSongA is successfully mocked while the others are just undefined.
With something like this, i'm pretty sure to achieve what i want
export function playSongA() {...}
export function playSongB() {...}
export function playSongC() {...}
The problem seems to be the module I try to mock is a function, and I want to partially mock the result of this function.
This is possible if you create a mock that returns the original, but also intercepts the function calls and replaces one of the values.
You can do this without a Proxy, but I choose to demonstrate that as it's the most complete method to intercept any use of the actual hook.
hooks/__mocks__/useSongPlayer.js
// Get the actual hook
const useSongPlayer = jest.requireActual('hooks/useSongPlayer');
// Proxy the original hook so everything passes through except our mocked function call
const proxiedUseSongPlayer = new Proxy(useSongPlayer, {
apply(target, thisArg, argumentsList) {
const realResult = Reflect.apply(target, thisArg, argumentsList);
return {
// Return the result of the real function
...realResult,
// Override a single function with a mocked version
playSongA: jest.fn(),
};
}
});
export default proxiedUseSongPlayer;

Jest test does not resolve a promise as it should. Is there a problem with my syntax or logic?

I am trying to test that a promise resolves and sets state properly according to what the asynchronous call returns. I have been trying to figure out this conundrum for days to almost no avail.
I have tried everything from runAllTimers() to runOnlyPendingTimers() to setTimeout() to simulate the time for the asynchronous call to finish. However, I didn't want to rely on setTimeout(), as that depends on real time instead of fake timers. While the test technically "passes" with setTimeout, I'm not so sure it's valid because when I console-log the state after the setTimeout, it does not update as it should and remains the same after I await the asynchronous call.
import 'item' from '../getItem'
jest.useFakeTimers();
it('should update state after fetchItemData fires', async () => {
const locationOfItem = {
pathname: '/item/path',
search: `?id=${itemId}`,
};
const props = {
someStore,
locationOfItem,
};
// The true is here because I use mobx for the store
const wrapper = createWrapper(ItemDataPage, props, true);
getItem.mockImplementationOnce(() =>
Promise.resolve({ item }),
);
await wrapper.fetchItemData();
// jest.runAllTimers();
// jest.runOnlyPendingTimers();
expect(getItem).toHaveBeenCalled();
// setTimeout(() => {
expect(wrapper.state.isLoading).toBe(false);
expect(wrapper.state.hasError).toBe(false);
expect(wrapper.state.item).toEqual({ item });
// }, 1000);
});
fetchItemData = async () => {
const { locationOfItem } = this.props;
const { search } = locationOfItem;
const id = search.replace('?id=', '');
try {
const item = await getItem(id);
} catch {
this.state.hasError = true;
} finally {
this.setState({isLoading: false, hasError: false, item,});
}
};
The expected results are that all assertions should be as stated in the code snippet. However, when I run this in any context outside of setTimeout(), it fails. Specifically, while getItem gets called properly, and hasError's state is false, the state of isLoading does not get set to false as it should after the promise resolves, and the state of item does not go from an empty object to a populated object as it should. In the method I'm testing, namely fetchItemData(), the method calls a service and sets the state of item to whatever data the service returns, which is an object. The original state of item is always an empty object. In short, isLoading remains true, and item remains an empty object. There is an error message saying "cannot read property "isInStock" of undefined. isInStock is a key inside of the item object, so I would expect that this property is not yet defined because item is still an empty object and does not get updated.
When testing react components, don't test methods. Test the desired implementations inside.
componentDidMount automatically runs once you create your wrapper. If fetchItemData is run inside componentDidMount, the test will do it automatically.
Mock the api call. Personally, I find it easier to understand when setting a return using mockMeturnValue or mockReturnValueOnce, as opposed to mockImplementation. Ultimately, at your discretion
// api test setup
jest.mock('./api/apiFile') // mocks every function in file
const getItem = jest.fn()
getItem.mockImplementationOnce(() =>
Promise.resolve({ item }),
);
const wrapper = createWrapper(ItemDataPage, props, true);
expect(getItem).toHaveBeenCalled();
expect(wrapper.state.isLoading).toBe(false);
expect(wrapper.state.hasError).toBe(false);
expect(wrapper.state.item).toEqual({ item });
You need to mock const getItem = jest.fn() before you create your wrapper.

Testing Sentry with Jest

I am testing my error boundaries to React and noticed in Codecov that there is a particular part of my Sentry function that hasn't been tested.
I have tried to use jest.mock("#sentry/browser") and mocking Sentry, however, can't seem to get the lines tested. The Sentry import is mocking correctly but not scope.
Here is an example of my attempt at mocking.
import * as Sentry from "#sentry/browser"
const mock_scope = jest.fn(() => {
return { setExtras: null }
})
Sentry.withScope = jest.fn().mockImplementation(mock_scope)
The untested lines are this callback function being passed to Sentry.withScope:
scope => {
scope.setExtras(errorInfo);
Sentry.captureException(error);
}
Since Sentry.withScope has been mocked you can use mockFn.mock.calls to retrieve the callback function passed to it.
Once you have retrieved the callback function, you can call it directly to test it.
Here is a slightly simplified working example:
import * as Sentry from '#sentry/browser';
jest.mock('#sentry/browser'); // <= auto-mock #sentry/browser
const componentDidCatch = (error, errorInfo) => {
Sentry.withScope(scope => {
scope.setExtras(errorInfo);
Sentry.captureException(error);
});
};
test('componentDidCatch', () => {
componentDidCatch('the error', 'the error info');
const callback = Sentry.withScope.mock.calls[0][0]; // <= get the callback passed to Sentry.withScope
const scope = { setExtras: jest.fn() };
callback(scope); // <= call the callback
expect(scope.setExtras).toHaveBeenCalledWith('the error info'); // Success!
expect(Sentry.captureException).toHaveBeenCalledWith('the error'); // Success!
});
Note that this line:
const callback = Sentry.withScope.mock.calls[0][0];
...is getting the first argument of the first call to Sentry.withScope, which is the callback function.
An addition to the accepted answer. The solution there requires to manually invoke the callback (see the callback(scope); // <= call the callback line in the test code).
Here is how to make it work automatically:
import * as Sentry from '#sentry/browser'
jest.mock('#sentry/browser')
// Update the default mock implementation for `withScope` to invoke the callback
const SentryMockScope = { setExtras: jest.fn() }
Sentry.withScope.mockImplementation((callback) => {
callback(SentryMockScope)
})
And then the test code becomes:
test('componentDidCatch', () => {
componentDidCatch('the error', 'the error info');
expect(SentryMockScope.setExtras).toHaveBeenCalledWith('the error info');
expect(Sentry.captureException).toHaveBeenCalledWith('the error');
});

React Jest - Mock a function with callback

I'm trying to write a test for following code
var throttle = require('lodash.throttle');
search = throttle(async (searchTerm:string) => {
const response = await AxiosWrapper.Instance.post(this.props.url, { "searchTerm": searchTerm });
this.setState({
searchResult: response.data as ISearchResult,
showSearchResult: true
});
},500);
So, my mock looks like
jest.mock("lodash.throttle", () => {
console.log("blah");
});
I would like to execute callback from throttle func.
If you look at the jest docs for jest.mock, the second argument is a factory (function) that returns the mock value for the module. In this case, we are mocking the lodash.throttle function, so we want a factory that returns a function. If you want to mock lodash.throttle such that it just calls the callback that is passed in, you would do
jest.mock("lodash.throttle", () => cb => cb())

How to unit test functions inside componentwillmount using jest and enzyme?

I'm trying to test a function inside componentWillMount.
component
componentWillMount = () => {
const {
agents,
match
} = this.props;
this.edit = false;
this.agent = {};
if (match.params.id) {
this.edit = true;
this.agent = getAgent(agents, match.params.id);
if ("undefined" === typeof this.agent) {
push("/agents");
}
}
resetStatusMessage();
formResetError();
};
render = () => {
const { form } = this.props;
const agent = this.agent;
this.avatar = agent.avatar;
...........................
}
I'am trying to test whether the getAgent function is called.And i also need to check the resetStatusMessage() and formResetError() were called.
Tests:
it("should call getAgent when mounted", () => {
const match = {
params: {
id: "1"
}
},
agents ={
loading: false,
byId : {
1:{
firstName: "abc",
lastName: "xyz"
}
},
avatar: "avatarUrl"
};
let mockGetAgent = jest.fn();
const store = configureStore();
const wrapper = mount(
<Provider store={store}>
<AgentForm match={match} getAgent={mockGetAgent}/>
</Provider>
);
expect(wrapper).toBeDefined();
expect(mockGetAgent).toBeCalled();
});
But my test failed with this message :
TypeError: Cannot read property 'avatar' of undefined
How can i solve this issue?In my react project am using jest and enzyme for testing.am new to react and enzyme.Any help will really appreciable.
Apologies, I didn't mean you need to pass it in as a prop. This will only work if the component normally receives the getAgent function as a prop.
I'm guessing that getAgent is a function defined within the same file as your component but outside of the component itself, and that you're only exporting the component?
If this is the case, when you mount the component it will look for getAgent within its scope and try to call it. At the moment, you've created a function called mockGetAgent but the component never makes a call to mockGetAgent. I think what you need to do is call your mock getAgent and get it to return something (e.g. An object that looks like one of your agents) so that this.agent isn't undefined
Also, a couple of notes on unit testing:
you should try to test your components in isolation. Here you're testing both Provider and AgentForm at the same time, but given that they each do specific things you should just try to test they're each doing their own job.
it's not very effective to test a component by checking that every function it uses gets called. You should try to check that the job the function does has been completed. E.g. if the getAgent function gets info about agents so that it can be rendered then you should check that your wrapper contains that info, rather than checking that getAgent was called

Resources