Difference between import and require in jest - react-native

I am writing my first test for a react-native project using react-native-router-flux and react-redux
My code is something like
jest.autoMockOff();
jest.setMock('react-native', {
NativeModules: {}
});
jest.setMock('react-native-router-flux', {
Actions: {}
});
const mockStore = require('../../mock/Store');
const actions = require('../myActions');
...
// Some tests that test if the right actions are dispatched.
The above works, However when I use ES6 import instead of require I have a problem.
If I replace
const actions = require('../myActions');
with
import * as actions from "../myActions"
I get the below error.
Runtime Error
- Error: Cannot find module 'ReactNative' from 'react-native.js'
at Resolver.resolveModule (node_modules/jest-cli/node_modules/jest-resolve/src/index.js:117:17)
at Object.<anonymous> (node_modules/react-native/Libraries/react-native/react-native.js:175:25)
at Object.<anonymous> (node_modules/react-native-router-flux/src/Scene.js:10:18)
While I can work with this, I am curious to understand the reason for failure,
Also note that I am just not able to transpile react-native-router-flux with es2015 presets in .bablerc file and I think I will have to live with that limitation (of not being able to transpile react-native-router-flux).
myActions.js looks like
import {Actions} from 'react-native-router-flux';
export function searchRequest() {
return {
type: "search_request"
}
}
export function searchRequestFailure(error) {
return {
type: "search_request_failure",
error: error.toString()
}
}
export function searchRequestSuccess(payload) {
return {
type: "search_request_success",
payload: payload
}
}
export function search(nameOrAddress) {
return dispatch => {
dispatch(searchRequest())
return fetch("http://localhost:8080/search", {
method: "GET"
}).then((response) => {
return response.json()
}).then((responseData) => {
dispatch(searchRequestSuccess(responseData))
Actions.list() //react-native-router-flux dependency
}).catch(error => {
dispatch(searchRequestFailure(error))
})
}
}
Using react-native 0.26 and jest 12.1.1

That is not the correct ES6 conversion.
const actions = require('../myActions'); // requires the defaultMember of the exported module and
//ES6 (ES2015) equivalent is
import actions from '../myActions';
https://developer.mozilla.org/en/docs/web/javascript/reference/statements/import

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,
// ... //
});

Upgraded to Vue 2.7 and now getting a bunch of warnings: [Vue warn]: Vue 2 does not support readonly arrays

Background
I recently upgraded from Vue v2.6.14 to Vue 2.7 by following this guide: https://blog.vuejs.org/posts/vue-2-7-naruto.html.
I made some changes (e.g., removing #vue/composition-api and vue-template-compiler, upgrading to vuex-composition-helpers#next, etc.).
Problem
The application loads for the most part, but now I get a ton of console errors:
[Vue warn]: Vue 2 does not support readonly arrays.
It looks like even just console.log(workspaces.value); (see code below) raises the warning.
Question
How do I resolve this issue?
Thank you!
Code
<script lang="ts">
import {
defineComponent,
onMounted,
computed,
} from 'vue';
import { createNamespacedHelpers } from 'vuex-composition-helpers';
import {
modules,
actionTypes,
getterTypes,
} from '#/store/types';
import _ from 'lodash';
const workspaceModule = createNamespacedHelpers(modules.WORKSPACE_MODULE);
export default defineComponent({
setup() {
const { newWorkspace, listWorkspaces } = workspaceModule.useActions([
actionTypes.WorkspaceModule.NEW_WORKSPACE,
actionTypes.WorkspaceModule.LIST_WORKSPACES,
]);
const { workspaces } = workspaceModule.useGetters([
getterTypes.WorkspaceModule.GET_WORKSPACES,
]);
onMounted(async () => {
await listWorkspaces({
Archived: false,
Removed: false,
});
console.log(workspaces.value);
});
return {
/*
workspacesSorted: computed(() => {
return _.orderBy(workspaces.value, ['LastUpdated'], ['desc']);
}),
*/
}
}
});
</script>
src/store/modules/workspace/getters.ts
import { GetterTree } from 'vuex';
import { WorkspaceState } from './types';
import { RootState } from '../../types';
import { getterTypes } from '../../types';
export const getters: GetterTree<WorkspaceState, RootState> = {
[getterTypes.WorkspaceModule.GET_WORKSPACES](context: WorkspaceState) {
return context.Workspaces;
},
[getterTypes.WorkspaceModule.GET_ALL_WORKSPACES](context: WorkspaceState) {
return context.AllWorkspaces;
}
}
src/store/modules/workspace/actions.ts
export const actions: ActionTree<WorkspaceState, RootState> = {
async [actionTypes.WorkspaceModule.LIST_WORKSPACES]({ commit }, payload: ListWorkspace) {
const wss = await list(payload.Archived, payload.Removed);
wss.forEach((ws) => {
ws.Archived = payload.Archived;
ws.Removed = payload.Removed;
});
commit(mutationTypes.WorkspaceModule.SET_WORKSPACES, wss);
},
};
src/store/modules/workspace/actions.ts
export const mutations: MutationTree<WorkspaceState> = {
[mutationTypes.WorkspaceModule.SET_WORKSPACES](ctx: WorkspaceState, wss: Workspace[]) {
ctx.Workspaces = wss;
},
};
src/service/useWorkspace.ts
const list = async(archived: boolean, removed: boolean) => {
const res = await get<Workspace[], AxiosResponse<Workspace[]>>('/workspace/list', {
params: {
archived,
removed,
}
});
return success(res);
};
When I call store.state.WorkspaceModule.Workspaces directly (either in the console or in computed), I get no errors:
import { useStore } from '#/store';
export default defineComponent({
setup() {
const store = useStore();
onMounted(async () => {
await listWorkspaces({
Archived: false,
Removed: false,
});
console.log(store.state.WorkspaceModule.Workspaces);
});
return {
workspacesSorted: computed(() =>
store.state.WorkspaceModule.Workspaces
),
}
}
});
This might be because workspaces is based on a getter, which are read-only. As mentioned in the blog you were referring to, readonly is not supported for arrays in Vue 2.7:
readonly() does create a separate object, but it won't track newly added properties and does not work on arrays.
It was (partially) supported for arrays in the Vue 2.6 Composition Api Plugin though:
readonly() provides only type-level readonly check.
So that might be causing the error. If it is mandatory for you, you might need to upgrade to vue3, or stick with 2.6 for a while. The composition Api plugin is maintained until the end of this year...
A workaround may be to skip the getter and access the state directly, since it is a quite simple getter which only returns the current state of Workspaces.
Hope this helps.

How do I mock this Vue injection?

I have a Vue 3 component that, when mounted in tests, cause warnings:
console.warn node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:40
[Vue warn]: injection "Symbol(VueToastification)" not found.
at <ModifyJob ref="VTU_COMPONENT" >
at <VTUROOT>
I assume it's this one complaining https://github.com/Maronato/vue-toastification/blob/master/composition/index.js#L30.
I have nearly 100 of these warnings, so it's kind of hard to read test-run output. I've tried to mock provide for this dependency, but I can't seem to succeed:
let provide = {}
provide[VueToastification] = VueToastification
provide['VueToastification'] = VueToastification
provide[Symbol(VueToastification)] = VueToastification
provide[Symbol('VueToastification')] = VueToastification
provide['Symbol(VueToastification)'] = VueToastification
let options = {
global: {
provide: provide,
}
}
mount(ModifyJob, options)
Is this some Vue2/Vue3 incompatibility or do I just not understand the docs at https://vue-test-utils.vuejs.org/v2/api/#global-provide ? Can someone help me get rid of these warnings, ideally by allowing me to inject a mock so I can test that toasts are made?
That error actually indicates that the plugin isn't installed in the test Vue instance. You could make VueToastification available to the component under test through the global.plugins mounting option:
import { shallowMount } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent.vue'
import VueToastificationPlugin from 'vue-toastification'
it('initializes', () => {
shallowMount(MyComponent, {
global: {
plugins: [VueToastificationPlugin]
}
})
})
Alternatively, if you want to verify that toast() (from VueToastification's useToast()) is called, you could mock vue-toastification:
import { shallowMount } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent.vue'
jest.mock('vue-toastification')
it('should call toast', () => {
const toast = jest.fn()
require('vue-toastification').useToast.mockReturnValueOnce(toast)
shallowMount(MyComponent).vm.showToast()
expect(toast).toHaveBeenCalled()
})
I solved setting a global list of plugins according to https://next.vue-test-utils.vuejs.org/api/#config-global:
// In a jest.setup.js file
import { config } from "#vue/test-utils";
import VueToastificationPlugin from "vue-toastification";
config.global.plugins = [VueToastificationPlugin];
// In your jest.config.js
module.exports = {
...
setupFilesAfterEnv: ["./jest.setup.js"],
};

Jest Mocking Permissions of Expo TypeError: Cannot read property 'askAsync' of undefined

I'm mocking expo and the Permissions module, but when calling Permissions.AskAsync Permissions is undefined.
Problem looks like this question. Using Jest to mock named imports
Used the provided answer, but did not work.
I have mocked the axios, which works. Doing the same for the expo module does not work.
The function I want to test:
checkPermission = async () => {
const {statusCamera} = await Permissions.askAsync(Permissions.CAMERA);
// console.log(statusCamera);
this.setState({cameraPermission: statusCamera});
const {statusCameraRoll} = await Permissions.askAsync(Permissions.CAMERA_ROLL);
this.setState({cameraRollPermission: statusCameraRoll});
};
The test:
describe("Test the Permission function", () => {
it('should return rejected permission.', async function () {
const wrapper = shallow(<Photo2/>);
const instance = wrapper.instance();
await instance.checkPermission();
expect(instance.state("cameraPermission")).toBeFalsy();
});
});
The mock I use for expo:
jest.mock('expo', ()=>({
Permissions: {
askAsync: jest.fn()
}
}))
and tried
(In file mocks/expo.js)
export default {
Permissions: {
askAsync: jest.fn(() => {
return "SOMETHING"
})
}
}
and tried
(In file mocks/expo.js)
jest.mock('expo', ()=>({
Permissions: {
askAsync: jest.fn()
}
}));
Error: "TypeError: Cannot read property 'askAsync' of undefined"
This error occures on line where Permissions.askAsyc is called. So Permissions is undefined. (Also checked it with console.log(Permissions)
I expected the instance.state("cameraPermission") to be falsy, but it crashes before it comes to that line.
Since expo change the packages to be import * as Permissions from 'expo-permissions';
You just need to create "mocks/expo-permissions.js" and have it has:
export const getAsync = jest.fn(permissions => Promise.resolve());
export const askAsync = jest.fn(permissions => Promise.resolve());
teerryn's answer is correct and is a good start. To add some more details:
Unless you've configured different roots for Jest, you should place your mock file in __mocks__/expo-permissions.js where __mocks__ is a directory at the same level as your node_modules folder. See Jest docs on mocking node modules.
The permissions argument being passed in will be undefined due to the way we're mocking the module, so you'll need to mock the permission types you want to use. Just need something simple like export const CAMERA_ROLL = 'camera_roll';
If you want to respond differently based on the permission type passed in (for example, allow Permissions.CAMERA but deny Permissions.CAMERA_ROLL and all other types), you can mock the implementation of the askAsync function. For example, your __mocks__/expo-permissions.js file would look like this:
export const CAMERA = 'camera';
export const CAMERA_ROLL = 'camera_roll';
export const askAsync = jest.fn().mockImplementation((permissionType) => {
const responseData = permissionType === CAMERA ? { status: 'granted' } : { status: 'undetermined' }; // you could also pass `denied` instead of `undetermined`
return Promise.resolve(responseData);
});
The problem is that you are handling async tests incorrectly (your checkPermission() function is async). There are several ways you can tell jest that you want to test an async function. Here are a few ways.
Here is a quick solution to your problem:
...
import { Permissions } from 'expo';
...
jest.mock('expo', () => ({
Permissions: {
askAsync: jest.fn(),
}
}));
...
describe("Test the Permission function", () => {
it('should return rejected permission.', () => {
Permissions.askAsync.mockImplementation( permission => { return {status: 'granted'}; } ); // if you want to add some sort of custom functionality
const wrapper = shallow(<Photo2/>);
const instance = wrapper.instance();
return instance.checkPermission().then(data => {
expect(instance.state("cameraPermission")).toBeFalsy();
});
});
});

How to unit test Vue.js components that use nuxt-i18n

If I try to run the thing below (with yarn run jest), I get TypeError: _vm.$t is not a function, because SearchField is using a translation ("$t('search')").
import { mount } from "#vue/test-utils";
import SearchField from "#/components/ui/SearchField";
describe("SearchField", () => {
const wrapper = mount(SearchField);
it("renders correctly", () => {
expect(wrapper.element).toMatchSnapshot();
});
});
If I add the following three lines at the beginning, I get TypeError: Cannot read property '_t' of undefined instead.
import Vue from "vue";
import VueI18n from "vue-i18n";
Vue.use(VueI18n);
nuxt-i18n is an external library, not your own code, so the test good practices ask us to just mock the translation library and its needed functions ($t in this case).
The following code should solve your problem:
describe("SearchField", () => {
const wrapper = mount(SearchField);
it("renders correctly", () => {
mocks: {
$t: (msg) => msg
}
expect(wrapper.element).toMatchSnapshot();
});
});
More information on this approach can be found here.