Vue testing beforeRouteEnter navigationGuard with JEST / Vue-test-utils - vue.js

i have a component which uses
beforeRouteEnter(to, from, next) {
return next((vm) => {
vm.cacheName = from.name
})
},
it takes previous route name and saves it into current component variable calld - cacheName
how can i test that with JEST? i was trying to
test('test', async () => {
await wrapper.vm.$options.beforeRouteEnter(null, null, () => {
return (wrapper.vm.cacheName = 'testname')
})
console.log(wrapper.vm.cacheName)
})
but it doesnt rly cover the test.. i guess i have to mock next function somehow but i just dont know how, please help me :<

Testing beforeRouteEnter:
I was trying with jest, In my use case, it worked with the below code.
vue-testing-handbook Reference Link
it('should test beforeRouteEnter', async () => {
const wrapper = shallowMount(Component, {
stubs,
localVue,
store,
router,
i18n,
});
const next = jest.fn();
Component.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next);
await wrapper.vm.$nextTick();
expect(next).toHaveBeenCalledWith('/');
});

Related

How to mock expo-camera requestCameraPermissionsAsync() response?

I am trying to do a unit test to one of my react native components.
One scenario requires me to mock expo-camera requestCameraPermissionsAsync() method but don't know how. What I'm trying to do is to mock the status to always have the granted value.
Initial approach, below:
jest.mock('expo-camera', () => {
const PermissionsCamera = jest.requireActual('expo-camera');
return {
...PermissionsCamera,
requestCameraPermissionsAsync: () =>
new Promise(resolve => resolve({granted: true, status: 'granted'})),
};
});
But that doesn't work. Need help, is there something wrong with the code above? Thank you
Update:
How I implemented in the component:
import {Camera} from 'expo-camera'
useEffect(() => {
(async () => {
const {status} = await Camera.requestCameraPermissionsAsync();
// additional logic when status is equal to 'granted'
})();
}, []);

Async custom hook from within useEffect

When kept in the component body, the following code works fine. Inside useEffect, it checks the asyncstorage and dispatches an action (the function is longer but other checks/dispatches in the function are of the same kind - check asyncstorage and if value exists, dispatch an action)
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
I'm trying to move it to a custom hook but am having problems. The custom hook is:
const useGetUserSettings = () => {
const dispatch = useDispatch();
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
};
export default useGetUserSettings;
Then in the component where I want to call the above, I do:
import useGetUserSettings from './hooks/useGetUserSettings';
...
const getUserSettings = useGetUserSettings();
...
useEffect(() => {
getUserSettings();
}, [getUserSettings])
It returns an error:
getUserSettings is not a function. (In 'getUserSettings()', 'getUserSettings' is undefined
I've been reading rules of hooks and browsing examples on the internet but I can get it working. I've got ESlint set up so it'd show if there were an invalid path to the hook.
Try the following.
useEffect(() => {
if (!getUserSettings) return;
getUserSettings();
}, [getUserSettings]);
The hook doesn't return anything, so it's not surprising that the return value is undefined ;)

React Native testing - act without await

Below test is passing but I get the following warning twice and I don't know why. Could someone help me to figure it out?
console.error
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
at printWarning (../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:120:30)
at error (../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:92:5)
at ../../node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14953:13
at tryCallOne (../../node_modules/react-native/node_modules/promise/lib/core.js:37:12)
at ../../node_modules/react-native/node_modules/promise/lib/core.js:123:15
at flush (../../node_modules/asap/raw.js:50:29)
import { fireEvent } from '#testing-library/react-native'
import { renderScreen } from 'test/render'
describe('screens/home', () => {
it('should render and redirect to the EventScreen', async () => {
const {
getByA11yLabel,
findByA11yLabel,
findAllByA11yLabel,
toJSON
} = renderScreen('Main')
expect(toJSON()).toMatchSnapshot('Default render')
const title = 'New event'
const titleInput = getByA11yLabel('event.title')
// Change title - sync fn
fireEvent.changeText(titleInput, title)
// Create button should be visible
const createButton = await findByA11yLabel('event.create')
expect(titleInput.props.value).toBe(title)
expect(createButton).toBeTruthy()
expect(toJSON()).toMatchSnapshot('Change title')
// Create event - async fn
fireEvent.press(createButton)
// The app should be redirected to the EventScreen
const titleInputs = await findAllByA11yLabel('event.title')
const upsertButton = await findByA11yLabel('event.upsert')
expect(toJSON()).toMatchSnapshot('Create event')
expect(titleInputs).toHaveLength(2)
expect(titleInputs[0].props.value).toBe('') // #MainScreen
expect(titleInputs[1].props.value).toBe(title) // #EventScreen
expect(upsertButton).toBeTruthy()
})
})
As far as I know, there is no need to wrap fireEvent with an act- link
findBy* also are automatically wrapped with act - link
Related issue in GitHub is still open
Dependencies:
react: 16.13.1
expo: 39.0.4
jest: 26.6.3
ts-jest: 26.4.4
jest-expo: 39.0.0
#testing-library/jest-native: 3.4.3
#testing-library/react: 11.2.2
#testing-library/react-native: 7.1.0
react-test-renderer: 16.13.1
typescript: 4.1.2
If you've exhausted all other debugging efforts and are pretty sure your code is written correctly, it may be related to react-native/jest-preset replacing global.Promise with a mock (see issue).
The solution to the problem, in this case, is to override/patch the jest preset to first save the original global Promise, apply the react-native/jest-preset and then restore the original Promise (overwriting the mocked version). This allowed me to use await in the tests that were unrelated to rendering without triggering the dreaded
console.error
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
This snippet shows one way to perform this patch: https://github.com/sbalay/without_await/commit/64a76486f31bdc41f5c240d28263285683755938
I was facing the same problem. For my case I was using useEffect in my component. And while test it prompted me to wrap the rendering inside an act() call. Once I did that i.e. act(async () => ...) my initial problem was solved but I was getting the above mentioned error (Warning: You called act(async () => ...) without await.). I had to use await act(async () => ...) in my test to fix that. Though I am still not sure why it was required.
For reference I am adding a complete example component and corresponding test using await act(async () => ...);
LocationComponent.tsx
/** #jsx jsx */
import { jsx } from 'theme-ui';
import { FunctionComponent, useEffect, useState } from 'react';
type Coordinate = {
latitude: number;
longitude: number;
};
const LocationComponent: FunctionComponent<any> = () => {
const [coordinate, setCoordinate] = useState<Coordinate>();
const [sharedLocation, setSharedLocation] = useState<boolean>();
useEffect(() => {
let mounted = true;
if (!coordinate && navigator) {
navigator.geolocation.getCurrentPosition(function (position) {
setCoordinate({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
});
navigator.permissions
.query({ name: 'geolocation' })
.then(function (result) {
if (mounted) setSharedLocation(result.state === 'granted');
});
}
return () => (mounted = false);
});
return (
<>
<div>Location shared:{sharedLocation ? 'Yes' : 'No'}</div>
<div>Latitude:{coordinate?.latitude}</div>
<div>Longitude:{coordinate?.longitude}</div>
</>
);
};
export default LocationComponent;
LocationComponent.spec.tsx
import React from 'react';
import { render, waitFor } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import LocationComponent from '../../../../../src/components/scheduler/location/LocationComponent';
const TEST_COORDS = {
latitude: 41.8817089,
longitude: -87.643301,
};
global.navigator.permissions = {
query: jest
.fn()
.mockImplementationOnce(() => Promise.resolve({ state: 'granted' })),
};
global.navigator.geolocation = {
getCurrentPosition: jest.fn().mockImplementationOnce((success) =>
Promise.resolve(
success({
coords: TEST_COORDS,
})
)
),
};
describe("Location Component when location share is 'granted'", () => {
it('should display current location details', async () => {
await act(async () => {
const { getByText } = render(<LocationComponent />);
/*expect(
await waitFor(() => getByText('Location shared:Yes'))
).toBeInTheDocument();*/
expect(
await waitFor(() => getByText('Latitude:41.8817089'))
).toBeInTheDocument();
expect(
await waitFor(() => getByText('Longitude:-87.643301'))
).toBeInTheDocument();
});
});
});

AddEventListener DOM event Jest testing

I have one Users vue component and I am trying to test mounted() with addEventListener.
Users.vue
=========
mounted(){
let viewPort = document.getElementById("Users-list"); ----> Here I am getting null for addEventListener.
viewPort!.addEventListener("scroll", (e: any) => {
let result =
e.target.scrollHeight - e.target.scrollTop - e.target.clientHeight ===
0;
if (result) {
this.test = this.Offset + this.Limit;
this.response = this.GetDetails();
}
});
}
I have written spec for Users component and trying to test mounted() method with addEventListener.
But I am getting an error message cannot read property addEventListener of null.
Users.spec.ts
=============
describe('Users TestSuite', async () => {
let userWrapper: any;
let userObj: any;
beforeEach(() => {
userWrapper = shallowMount(Users, {
// attachTo: document.getElementById('Users-list'),
localVue,
i18n,
router
})
userObj = userWrapper.findComponent(Users).vm;
const mockAddeventListener = jest.fn().mockImplementation((event, fn) => {
fn();
})
document.getElementById = jest.fn().mockReturnValue({
scrollTop: 100,
clientHeight: 200,
scrollHeight: 500,
addEventListener: mockAddeventListener
})
expect(mockAddeventListener).toBeCalledWith('scroll', expect.anything());
});
it('should render Users page', () => {
expect(userObj).toBeTruthy();
});
I think the problem here might be u are creating the mock function after u are creating the component. Mounted method will be called when the wrapper is created so try to move the mock implementation above the wrapper statement.
Another sure way in which to make it work is before u create the wrapper set the body of the document like document.body.innerHTML = <div id="users-list"></div>. This will definitely work.
For both the above solutions make sure that they are above the wrapper statement.

vue test url fails on the clicked Button

I wanted to create a simple Test on Vuejs.
The purpose is to click a router-link and prove that the pathname s correct.
Here is the Code:
describe("Menu.vue", () => {
const localVue = createLocalVue();
localVue.use(VueRouter);
test("click on first routes to ./first", async () => {
const wrapper = shallowMount(Menu, {
localVue,
router,
stubs: {
RouterLink: RouterLinkStub,
First
}
});
await wrapper.find("#testFirst").trigger("click");
await Vue.nextTick();
expect(wrapper.find("#testFirst").exists()).toBe(true);
await flushPromises();
expect(location.pathname).toBe("/first")
});
});
The main component is a Menu.vue with router link id testFirst, the error comes from the last line where the expect fails.
The other component is named first.vue.
The first expect passes proving that the #testFirst exists.
it expects"/first" and received "/"
Thanks
Instead of checking location pathname you can test
expect(wrapper.vm.$route.push).toHaveBeenCalledWith({YOUR ROUTE})
For this, you have to mock the $route push method that you can do in stubs while creating a wrapper for your componenet.
mocks: {
$router:{
push:jest.fn()
}
}