Jest - test function declared in useContext AppProvider - react-native

I'm new to jest and trying to test my React Native app. I have what seems like a very simple case: a function called getPastWeek in my AppProvider which I want to test by passing it an argument then checking the return value. However, I am struggling to import the getPastWeek function into my test file:
context.js
const AppProvider = ({children}) => {
const getPastWeek = (param) => {
return param + 5;
}
return (<AppContext.Provider value={{getPastWeek}}>{children}</AppContext.Provider>)
}
export const useGlobalContext = () => useContext(AppContext)
export {AppProvider, AppContext}
context.test.tsx
const { getPastWeek } = useGlobalContext();
it("Test description", () => {
expect(
getPastWeek([{ data: () => ({ date: new Date() }) }]).toBe(true)
);
});
However, this does not work since useGlobalContext needs to be called from within a AppProvider. How can I achieve this desired behaviour and test the getPastWeek function?
EDIT: After doing some digging it seems like one way to achieve this would be to create a test component wrapped in the AppProvider in the test file. However, this seems like overkill to test a single function. Is there a simpler way to do this?
I guess I could also export the function but it seems a little messy to do so

Related

Testing custom hooks that are using next-i18next useTranslation hook

I have created a custom hook that returns the translated value using the useTranslation hook.
import { useTranslation } from "next-i18next";
export const useCustomHook = (data) => {
const {t, i18n: { language: locale }} = useTranslation();
const value = {
field: t("some.key.from.json.file", { arg: data.arg }),
field2: data.name,
field3: t("another.key", {
arg: data.arg2, count: 3
})
}
return value;
};
I want to create a unit test for this custom hook, but I can't get the useTranslation hook to work as it does when running the app itself. Further info my current setup is as follows:
1- I'm using Nextjs with next-i18next library.
2- No i18n provider to wrap the app, only using HOC from next-i18next to wrap _app.
3- I have 2 json files for locales.
Is there a way to allow the useTranslation hook to work and get the parsed value from the translation file? here's what I tried so far too:
1- mocking the useTranslation hook, but this returns the ("another.key") as is without the parsed value.
2- I tried to create a wrapper with i18n-next provider, but that didn't work too.
Here's my test file.
describe("useCustomHook()", () => {
it("Should return correctly mapped props", () => {
const { result } = renderHook(() =>
useCustomHook(mockData)
);
const data = result.current[0];
expect(data.field).toBe(mockData.field); // this returns ("some.key.from.json.file") it doesn't use the t function,
// ... //
});

Testing a nested custom hook with react-testing-library

I'm trying to test a hook in isolation with #testing-library/render-hooks in a React Native project. The hook I have looks something like this:
const useStatus = () => {
const [ status, setStatus ] = useState(null)
const { data: data1 } = useDataHook1()
const { data: data2 } = useDataHook2()
useEffect(() => {
if (data1 && data2) {
// Some logic to determine status based on data1 + data2
setStatus(...)
}
}, [data1, data2])
return { status }
}
So I want to test useStatus and I'd like to mock useData1 and useData2, all three hooks of which are defined in the same file. My test looks like so:
import * as hooks from 'path/to/hooks'
const data1Spy = jest.spyOn(hooks, 'useData1')
const data2Spy = jest.spyOn(hooks, 'useData2')
describe('useStatus', () => {
it('should render', async () => {
data1Spy.mockReturnValue({ data: 'foo' }
data2Spy.mockReturnValue({ data: 'bar' }
const { result, waitForNextUpdate } = renderHook(() => hooks.useStatus())
await waitForNextUpdate()
console.log(result.current.status)
}
}
The console logs values that I would expect if the nested data hooks weren't returning, and the error:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
appears, which tells me that the mocks aren't being set correctly. (Thinking the SWR queries are trying to dispatch in a test environment, leading to unresolved promises floating around after the test finishes.)
Some things I've tried:
Using mockReturnValue as seen in the above code
Using mockImplementation instead of mockReturnValue
Using jest.mock + jest.requireActual pattern as is done here
I'm not sure what else to try. I've been able to mock hooks before using the above pattern when rendering a component, but this is my first time working with rendering the hook directly. Any thoughts?

How Test with Jest a function in the method "mounted" VueJS

I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for themethods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.

How to simulate calling a utility function using jest?

I'm testing a utility function that returns an integer, I'm trying to simulate calling it, but I can't find the right way to do so even after hours of googling.
I've also tried spyOn() and it didn't seem to work.
Authentication.js
export function auth(username) {
AsyncStorage.getItem('#app:id').then((id) => {
if (id === username) {
return 1;
}
return 0;
});
}
Authentication.test.js
import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer'; // Note: test renderer must be required after react-native.
import mockAxios from 'axios';
import mockAsyncStorage from '#react-native-community/async-storage';
import auth from '../App/Utils/Authorization';
test('should check whether the user whose username is entered as a paramter is the same as the user logged in the application', () => {
auth = jest.fn();
expect(auth).toHaveReturned();
expect(mockAsyncStorage.getItem).toHaveBeenCalledTimes(1);
expect(mockAsyncStorage.multiRemove).toHaveBeenCalledWith('#app:id');
});
I expected a simulation of calling auth() and a successful test, instead, I'm getting an error "auth" is read-only as an output whenever running yarn test.
You are reassigning the imported member auth and not using jest.fn() in the way that is supposed to work. Calling the jest mock function would return undefined, instead with mockFn.mockImplementation(fn) you could bind your function to the mock and test that it's being called or returning some expecting value.
import auth from '../App/Utils/Authorization';
test('Test auth', () => {
const mockAuth = jest.fn().mockImplementation(auth);
mockAuth();
expect(mockAuth).toHaveBeenCalled();
}
You can validate the output of your function checking mockFn.mock.results, which stores the results of every call made over your call function.
test('when user is provided', () => {
mockAuth({ user: {id: 'test', name: 'test'} });
expect(mockAuth).toHaveBeenCalled();
const result = mockAuth.mock.results[0].value;
expect(result).toBe(1);
});

Testing a function called on an object with Jest in React Native

EDIT
Current example,
it('CALLED THE canOpenURL FUNCTION', () => {
const wrapper = mount(<ResourceCardComponent {...mockProps} />);
const canOpenURLSpy = jest.spyOn(Linking, 'canOpenURL');
wrapper.find('TouchableOpacity').simulate('click');
expect(canOpenURLSpy).toHaveBeenCalled();
canOpenURLSpy.mockReset();
canOpenURLSpy.mockRestore();
});
Error
expect(jest.fn()).toHaveBeenCalled() Expected mock function to have
been called.
Problem
I am using Jest & Enzyme to test a class made with React Native. This class has a function inside of it that when fired off uses the Linking library to call canOpenUrl and openUrl. I can simulate the click event on the mounted component but I am having trouble knowing how much of this I can actually test.
My goal is to check if Linking.canOpenUrl ever fires off.
Exmaple
The function inside the component looks like this,
onPressLink() {
console.log('HEY THIS FUNCTION FIRED WOOT WOOT');
Linking.canOpenURL(this.props.url).then((supported) => {
if (supported) {
Linking.openURL(this.props.url);
}
});
}
I can simulate this firing off like this,
describe('onPressLink has been called!', () => {
it('It clicks the mock function onPressLink!', (done) => {
const wrapper = mount(<MyComponent {...mockProps} />);
const onPressLink = jest.fn();
const a = new onPressLink();
wrapper.find('TouchableOpacity').first().simulate('click');
expect(onPressLink).toHaveBeenCalled();
done();
});
});
Now that does work, but my goal is to use something like this,
expect(Linking.canOpenUrl).toHaveBeenCalled();
But I keep getting this error,
TypeError: Cannot read property '_isMockFunction' of undefined
Current code that is trying to check if this function is ever fired off. Which is inside the parent function that is clicked with the simulate method,
it('calls canOpenURL', () => {
const wrapper = mount(<MyComponent {...mockProps} />);
const canOpenURL = jest.spyOn(wrapper.instance, 'onPressLink');
wrapper.find('TouchableOpacity').simulate('click');
expect('Linking.canOpenUrl').toHaveBeenCalled();
});
Question
What is the proper way to check to see if Linking.canOpenURL is fired when its parent function is executed?
(Since Jest 19.0.0+)
You can spy on the Linking module methods using jest.spyOn().
(1) Tell jest to spy on the module method:
const spy = jest.spyOn(Linking, 'canOpenURL');
(2) After doing everything you need to test it, check the spy:
expect(spy).toHaveBeenCalled();
(3) Clean up and stop spying on the module method
spy.mockReset();
spy.mockRestore();
If you don't want the tests to use the actual implementation of the methods, you can fake them like this:
jest.spyOn(Linking, 'canOpenURL').mockImplementation(() => Promise.resolve());
Where the function passed to mockImplementation will be whatever you want the method to do when called.
Ref https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname
When using the actual implementation of your module method, which is asynchronous, the promise might not have been resolved by the time you tested it. You need to make sure any promise is resolved in your method implementation before making any assertions on it.
One way to deal with this is using async/await, like so:
it('...', async () => {
// Wait for promise to resolve before moving on
await wrapper.instance().onPressLink();
// make your assertions
expect(...);
});
Another option is using expect().resolves, available since Jest 20.0.0, where you wait for some promise in the argument to expect() to resolve with a value before making an assertion on that value.
expect(somePromiseThatEventuallyResolvesWithValue).resolves.toBe(Value);
I've done in simplest way:
Steps to spy:
Make spy object for original function using jest
Call original function with / without argument(s)
Assert the function which should be called with valid argument(s)
Reset mock
Restore mock
Here is the sample example
DefaultBrowser.ts which is actual class.
import { Linking } from 'react-native';
export const openDefaultBrowser = async url => {
if (await Linking.canOpenURL(url)) {
Linking.openURL(url);
}
};
DefaultBrowser.test.ts which is test case class.
import { openDefaultBrowser } from '../DefaultBrowser';
import { Linking } from 'react-native';
describe('openDefaultBrowser with validate and open url', () => {
it('validate url', async () => {
const spy = jest.spyOn(Linking, 'canOpenURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
it('open url', async () => {
const spy = jest.spyOn(Linking, 'openURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
});
Hope this helps you.
it('open url', async () => {
jest.spyOn(Linking, 'canOpenURL')
const spy = jest.spyOn(Linking, 'openURL')
openURL(sitePath)
await waitFor(() => {
expect(spy).toHaveBeenCalledWith(sitePath)
})
spy.mockReset()
spy.mockRestore()
})