I have a component that's a default export
// Component.js
export default () => <>getData()</>;
export const getData = async () => await fetch('/');
// Component.test.js
import Component from 'Component'
describe('test getData', () => {
const getDataMock = jest.spyOn(Component, 'getData');
expect(getDataMock).toBeCalledOnce();
})
I get error: Cannot spy the getData property because it is not a function; undefined given instead
you imported the default function instead of getData.
// Component.test.js
import mydefault, * as notdefaults from 'Component';
test('test getData', () => {
const getDataMocked = jest.spyOn(notdefaults, 'getData');
getDataMocked.mockResolvedValue('hello!');
expect(mydefault()).toBeDefined(); // call the default component to trigger getData()
expect(getDataMocked).toBeCalledTimes(1);
});
Related
All I wanna do is be able to call logic from my geolocationApi file into my react-native components whenever I want, NOT LIKE A HOOK but normal async functions, I'm using a custom hook in the geolocationApi file I'm importing though! (custom hooks handles mobx state updates)
I want to call it like this in my functional components (plain and easy):
import geolocationApi from '#utils/geolocationApi.js'
const getCoords = async () =>
{
let result = await geolocationApi().requestLocationPermissions(true);
};
My geolocationApi file where I have a bunch of functions about geolocation I don't want to crowd my components with.
#utils/geolocationApi.js
import _ from 'lodash';
import Geolocation from 'react-native-geolocation-service';
import { useStore } from '#hooks/use-store';
const geolocationApi = () => {
//Custom hook that handles mobx stores
const root = useStore();
const requestLocationPermissions = async (getCityName = false) =>
{
const auth = await Geolocation.requestAuthorization("whenInUse");
if(auth === "granted")
{
root.mapStore.setLocationEnabled(true);
let coords = await getMe(getCityName);
return coords;
}
else
{
root.mapStore.setLocationEnabled(false);
}
};
const getMe = async () =>
{
Geolocation.getCurrentPosition(
async (position) => {
let results = await onSuccess(position.coords);
return results;
},
(error) => {
console.log(error.code, error.message);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
};
/*const onSuccess = async () => {}*/
};
export default geolocationApi;
This can't be that hard!
If I remove export default geolocationApi and instead add export const geolocationApi at the top I get:
geolocationApi.default.requestLocationPermissions is not a function
You cannot use hooks outside React components. You can pass down the state to your function
import geolocationApi from '#utils/geolocationApi.js'
const getCoords = async (root) =>
{
let result = await geolocationApi(root).requestLocationPermissions(true);
};
Then instead of using useStore()
import _ from 'lodash';
import Geolocation from 'react-native-geolocation-service';
import { useStore } from '#hooks/use-store';
// pass the root from top
const geolocationApi = (root) => {
// your logic
return {
requestLocationPermissions,
getMe
}
}
Then somewhere in your component tree, ( an example with useEffect )
import getCoords from 'path'
const MyComp = () => {
const root = useStore();
useEffect(() => {
getCoords(root)
}, [root])
}
As you said, geolocationApi is a regular function, not a React component/hook. So, it isn't inside the React lifecycle to handle hooks inside of it.
You can use the Dependency Injection concept to fix it.
Make geolocationApi clearly dependent on your store.
const geolocationApi = (store) => {
Then you pass the store instance to it.
const getCoords = async (store) =>
{
let result = await geolocationApi(store).requestLocationPermissions(true);
};
Whoever React component calls the getCoords can pass the store to it.
//...
const root = useStore();
getCoords(root);
//...
In my React Native application, i am trying to add a component where i'll perform some config tasks but that component won't render anything. I have made the component already but when i import that on App.tsx the fucntion doesn't get called. How to import this following component properly to App.tsx. The component is given below:
var androidVersion = VersionInfo.appVersion;
var iosVersion = VersionInfo.buildVersion;
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const Config = () => {
console.log('check if get called >>',showVersionCodeStatus);
const prevAmount = usePrevious(iosVersion);
useEffect(() => {
if(prevAmount.iosVersion !== iosVersion) {
// process here
}
if(prevAmount.androidVersion !== androidVersion) {
// process here
}
}, [iosVersion,androidVersion])
// return {showVersionCodeStatus};
}
export default Config;
i'm importing the component in my App.tsx like the following:
import './config';
But it doesn't call the Config function. I have tried the following too:
import Config from './config';
That doesn't seem to work too. What am i doing wrong?
Since Config does not render anything, you should export it as a custom hook with a name such as useConfig. Subsequently you can import and use your custom hook in App.tsx, which will then run the config tasks specified in your useConfig custom hook.
useConfig.ts
var androidVersion = VersionInfo.appVersion;
var iosVersion = VersionInfo.buildVersion;
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const useConfig = () => {
console.log('check if get called >>',showVersionCodeStatus);
const prevAmount = usePrevious(iosVersion);
useEffect(() => {
if(prevAmount.iosVersion !== iosVersion) {
// process here
}
if(prevAmount.androidVersion !== androidVersion) {
// process here
}
}, [iosVersion,androidVersion])
// return {showVersionCodeStatus};
}
export default useConfig;
App.tsx
import useConfig from "./useConfig";
export default function App() {
const config = useConfig();
return (
...
);
}
I'm trying to mock an axios get function but I'm getting TypeError: moduleName.startsWith is not a function when mocking "axios" What would be the right way to mock it?
Error:
FAIL src/tests/Sample.test.jsx
● Test suite failed to run
TypeError: moduleName.startsWith is not a function
5 | import { backendUrl } from "../helper/constants"
6 | describe("test useEffect and axios", () => {
> 7 | const mockGet = jest.mock(axios.get)
Sample.test.jsx
import { mount } from "enzyme"
import { screen, render, act } from "#testing-library/react"
import Sample from "../pages/Sample"
import axios from "axios"
import { backendUrl } from "../helper/constants"
describe("test useEffect and axios", () => {
const mockGet = jest.mock(axios.get) // THROWS ERROR HERE
let wrapper
it("should call axios", async () => {
await act(async () => {
mockGet.mockImplementationOnce(() => Promise.resolve({}))
wrapper = mount(<Sample />)
})
wrapper.update()
await expect(mockGet).toHaveBeenCalledWith(backendUrl)
})
})
Sample.jsx
import axios from "axios"
import { useState, useEffect } from "react"
import { backendUrl } from "../helper/constants"
const Sample = () =>{
const [pets, setPets] = useState([])
useEffect(() => axios.get(backendUrl)
.then(({data}) =>setPets(data.entries))
.catch((err)=>console.log(err)), [])
return (
<>
<p>I h8 all of you</p>
{pets.map((e, i) =><h2 key={i}>{e.Link}</h2>)}
</>
)
}
export default Sample
You have to use the right syntax like this jest.mock('axios') so in your case:
jest.mock('axios')
describe("test useEffect and axios", () => {
const mockGet = jest.fn()
let wrapper
it("should call axios", async () => {
await act(async () => {
mockGet.mockImplementationOnce(() => Promise.resolve({}))
wrapper = mount(<Sample />)
})
wrapper.update()
await expect(mockGet).toHaveBeenCalledWith(backendUrl)
})
})
I have a plugin that console.logs data.
logData.spec.js
import Vue from 'vue'
import { createLocalVue } from '#vue/test-utils'
import logData from './logData'
describe('logData plugin', () => {
const localVue = createLocalVue()
it('adds a $logData method to the Vue prototype', () => {
expect(Vue.prototype.$logData).toBeUndefined()
localVue.use(logData)
expect(typeof localVue.prototype.$logData).toBe('function')
})
it('console.logs data passed to it', () => {
const data = 'data to be logged'
const localVue = createLocalVue()
localVue.use(logData)
expect(localVue.prototype.$logData(data)).toBe('data to be logged')
})
})
logData.js
export function logData (dataToLog) {
const isLoggingData = localStorage.getItem('isLoggingData')
if (isLoggingData) {
console.log(dataToLog)
}
}
export default {
install: function (Vue) {
Vue.prototype.$logData = logData
}
}
The error I get is in my unit test is Expected: 'data to be logged", Received: undefined. Why is the second test being read as undefined?
It's expected behavior since console.log() returns undefined. To get desired result you should add this line of code to your lodData function:
return dataToLog
export function logData (dataToLog) {
const isLoggingData = localStorage.getItem('isLoggingData')
if (isLoggingData) {
console.log(dataToLog)
return dataToLog
}
}
NOTICE: Also you don't have localStorage in your test environment.
I'm trying to mock an async function that is exported as a default export but all I get is TypeError: Cannot read property 'then' of undefined
What I'm trying to mock is config.js:
const configureEnvironment = async (nativeConfig) => {
return { await whatever() }
}
The file I'm testing is Scene.js:
import configureEnvironment from './config';
class Scene extends React.Component {
constructor(props) {
nativeConfig = {};
configureEnfironment(nativeConfig).then((config) => {
// Do stuff
}
}
}
And my test file is Scene.test.js:
let getScene = null;
const configureEnvironmentMock = jest.fn();
describe('Scene', () => {
jest.mock('./config', () => configureEnvironmentMock);
const Scene = require('./Scene').default;
getScene = (previousState) => {
return shallow(
<Scene prevState={previousState}>
<Fragment />
</Scene>,
);
};
it('calls configureEnvironment with the nativeConfig', async () => {
expect.assertions(1);
const nativeConfig = {};
getScene(nativeConfig);
expect(configureEnvironmentMock).toHaveBeenCalledWith(nativeConfig);
});
});
However, the result of running the test is:
TypeError: Cannot read property 'then' of undefined
I understand the issue is on the way I mock configureEnvironment but I cannot get it working.
I also tried to mock the function like:
jest.mock('./config', () => {
return {
default: configureEnvironmentMock,
};
});
But it results on:
TypeError: (0 , _config2.default) is not a function
A clean and simple way to mock the default export of a module is to use jest.spyOn in combination with functions like mockImplementation.
Here is a working example based on the code snippets above:
config.js
const whatever = async () => 'result';
const configureEnvironment = async (nativeConfig) => await whatever();
export default configureEnvironment;
Scene.js
import * as React from 'react';
import configureEnvironment from './config';
export class Scene extends React.Component {
constructor(props) {
super(props);
configureEnvironment(props.prevState).then((config) => {
// Do stuff
});
}
render() {
return null;
}
}
Scene.test.js
import React, { Fragment } from 'react';
import { shallow } from 'enzyme';
import { Scene } from './Scene';
import * as config from './config';
describe('Scene', () => {
const mock = jest.spyOn(config, 'default'); // spy on the default export of config
mock.mockImplementation(() => Promise.resolve('config')); // replace the implementation
const getScene = (previousState) => {
return shallow(
<Scene prevState={previousState}>
<Fragment />
</Scene>,
);
};
it('calls configureEnvironment with the nativeConfig', async () => {
expect.assertions(1);
const nativeConfig = {};
getScene(nativeConfig);
expect(mock).lastCalledWith(nativeConfig); // SUCCESS
});
});
You can mock anything with jest, like this
jest.mock('#material-ui/core/withWidth', () => ({
__esModule: true,
isWidthUp: jest.fn((a, b) => true),
default: jest.fn(fn => fn => fn)
}))