I'm building an android app using React-native and using PermissionsAndroid from react-native to get user permission.
import {PermissionsAndroid} from 'react-native'
Now i'm writing unit test and i need to verify the component behaviour based on the Permission.
hence i need to mock PermissionsAndroid.
Is there a way to do this?
jest.mock('react-native//Libraries/PermissionsAndroid/PermissionsAndroid', () => {
const PermissionsAndroid = jest.requireActual(
'react-native//Libraries/PermissionsAndroid/PermissionsAndroid',
);
console.log(PermissionsAndroid);
return {
...PermissionsAndroid,
check: jest.fn(() => new Promise(resolve => resolve(true))),
request: jest.fn(() => new Promise(resolve => resolve(true))),
};
});
This worked for me in 2022
jest.mock('react-
native//Libraries/PermissionsAndroid/PermissionsAndroid', () => {
return {
...jest.requireActual('react- native//Libraries/PermissionsAndroid/PermissionsAndroid'),
request: jest.fn(() => new Promise(resolve => resolve('granted')))
}
})
Because it is async you have to later on await it e.g. with react testing library const element = await findByText(...)
Simply mocking
jest.doMock('react-native', () => ({ PermissionsAndroid: {... did not work for me. Here is how I got it to work specifically mocking requestMultiple and check.
let fineLocationPermissionResult: String = PermissionsAndroid.RESULTS.GRANTED;
let courseLocationPermissionResult: String = PermissionsAndroid.RESULTS.GRANTED;
let fineLocationPermissionGranted = true;
let coarseLocationPermissionGranted = true;
const permissionsAndroidModule = jest.requireActual('react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js');
jest.doMock('react-native/Libraries/PermissionsAndroid/PermissionsAndroid', () => ({
...permissionsAndroidModule,
requestMultiple: () => {
return {
[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION]: fineLocationPermissionResult,
[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION]: courseLocationPermissionResult,
};
},
check: () => {
return fineLocationPermissionGranted && coarseLocationPermissionGranted;
},
}));
I've included some of the variables I used in my tests to manipulate the results of the mock but essentially you need to mock the entire module path ('react-native/Libraries/PermissionsAndroid/PermissionsAndroid') and then include the rest of the module that you are not mocking via jest.requireActual.
Solution below:
jest.mock(
'react-native//Libraries/PermissionsAndroid/PermissionsAndroid',
() => ({
PermissionsAndroid: {
request: () => {
true;
},
check: () => {
true;
},
},
})
);
You can mock this from react-native directly, just like:
jest.doMock('react-native', () => ({
PermissionsAndroid: {
request: (permission: string) => {
//whatever you want
},
},
}))
Notice that you might see some issues with the components you are using for that unit test, i.e. it might show an error if you are using <View> from React Native and not mocking it. Given that case, you have to import <View> and then include it in your mock.
import { View } from 'react-native'
...
jest.doMock('react-native', () => ({
View,
PermissionsAndroid: {
request: (permission: string) => {
//whatever you want
},
},
}))
Related
I am working on react native application and want to integrate the Phone masking feature like Uber do. I have choosen Twilio Phone Masking for this. I have used react-native-twilio-programmable-voice package.
I have integrated this using this link:: https://medium.com/#edzh1/create-a-twilio-voip-calls-in-a-react-native-app-35a729a9613d
I have done server setup successfully, using php. But getting error deviceNotReady error : "Registration failed". I have no idea what I am doing wrong here.
This is initial function I am calling here::
initTwilio = async () => {
const token = await this.getAuthToken();
if (Platform.OS === 'android') {
await this.getMicrophonePermission();
}
const success = await TwilioVoice.initWithToken(token);
if (success.initialized) {
TwilioVoice.addEventListener('deviceReady', () => {
this.setState({ twilioInited: true });
});
TwilioVoice.addEventListener('deviceNotReady', function (data) {
console.log('data', data) // getting error here
});
if (Platform.OS === 'ios') { //required for ios
TwilioVoice.configureCallKit({
appName: 'ReactNativeTwilioExampleApp',
});
}
}
};
getAuthToken = () => {
return fetch('https://myurl/accessToken.php', {
method: 'get',
})
.then(response => response.text())
.catch((error) => console.error(error));
}
Please help, and suggest me what I am doing wrong here.
expo-camera: "^8.0.0"
sdkVersion: "36.0.0"
Hello people, when i try:
import { Camera } from 'expo-camera';
...
const cameraIsAvailable = await Camera.isAvailableAsync()
const availablesCameraTypes = await Camera.getAvailableCameraTypesAsync()
console.log("cameraIsAvailable: ", cameraIsAvailable)
console.log("availablesCameraTypes: ", availablesCameraTypes)
i get the fallowing errors:
expo-camera.isAvailableAsync is not available on android, are you sure you've linked all the native dependencies properly?
The method or property expo-camera.getAvailableCameraTypesAsync is not available on android, are you sure you've linked all the native dependencies properly?
the problem just disappear when i remove:
state = {
...
cameraType: Camera.Constants.Type.front,
};
...
<Camera
type={this.state.cameraType}
flashMode={flashMode}
style={styles.preview}
ref={camera => this.camera = camera}
/>
and change it by:
state = {
...
cameraType: Camera.Constants.Type.back,
};
and i change "cameraType" by
componentDidMount = () => {
this.props.navigation.addListener('didFocus', async () => {
await setTimeout(() => {
this.setState({ cameraType: Camera.Constants.Type.front })
}, 100)
});
}
it seems its an error from expo-camera...
so when i try to call these methods:
const cameraIsAvailable = await Camera.isAvailableAsync()
const availablesCameraTypes = await Camera.getAvailableCameraTypesAsync()
i get following errors: errors: expo-camera.isAvailableAsync and expo-camera.getAvailableCameraTypesAsync is not available on android
The methods you're trying to use, Camera.isAvailableAsync and Camera.getAvailableCameraTypesAsync are marked in the documentation as Web only, so calling them will only work, well, on Web.
In code run in react-native context (as opposed to browser context) just check permissions and you should be good to go!
Is there any way to abort a fetch request on react-native app ?
class MyComponent extends React.Component {
state = { data: null };
componentDidMount = () =>
fetch('http://www.example.com')
.then(data => this.setState({ data }))
.catch(error => {
throw error;
});
cancelRequest = () => {
//???
};
render = () => <div>{this.state.data ? this.state.data : 'loading'}</div>;
}
i tried the abort function from AbortController class but it's not working !!
...
abortController = new window.AbortController();
cancelRequest = () => this.abortController.abort();
componentDidMount = () =>
fetch('http://www.example.com', { signal: this.abortController.signal })
....
Any help please !
You don't need any polyfill anymore for abort a request in React Native 0.60 changelog
Here is a quick example from the doc of react-native:
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* #format
* #flow
*/
'use strict';
const React = require('react');
const {Alert, Button, View} = require('react-native');
class XHRExampleAbortController extends React.Component<{}, {}> {
_timeout: any;
_submit(abortDelay) {
clearTimeout(this._timeout);
// eslint-disable-next-line no-undef
const abortController = new AbortController();
fetch('https://facebook.github.io/react-native/', {
signal: abortController.signal,
})
.then(res => res.text())
.then(res => Alert.alert(res))
.catch(err => Alert.alert(err.message));
this._timeout = setTimeout(() => {
abortController.abort();
}, abortDelay);
}
componentWillUnmount() {
clearTimeout(this._timeout);
}
render() {
return (
<View>
<Button
title="Abort before response"
onPress={() => {
this._submit(0);
}}
/>
<Button
title="Abort after response"
onPress={() => {
this._submit(5000);
}}
/>
</View>
);
}
}
module.exports = XHRExampleAbortController;
I've written quite a bit actually about this subject.
You can also find the first issue about the OLD lack of AbortController in React Native opened by me here
The support landed in RN 0.60.0 and you can find on my blog an article about this and another one that will give you a simple code to get you started on making abortable requests (and more) in React Native too. It also implements a little polyfill for non supporting envs (RN < 0.60 for example).
You can Actually achieve this by installing this polyfill abortcontroller-polyfill
Here is a quick example of cancelling requests:
import React from 'react';
import { Button, View, Text } from 'react-native';
import 'abortcontroller-polyfill';
export default class HomeScreen extends React.Component {
state = { todos: [] };
controller = new AbortController();
doStuff = () => {
fetch('https://jsonplaceholder.typicode.com/todos',{
signal: this.controller.signal
})
.then(res => res.json())
.then(todos => {
alert('done');
this.setState({ todos })
})
.catch(e => alert(e.message));
alert('calling cancel');
this.controller.abort()
}
render(){
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button title="Do stuff" onPress={() => { this.doStuff(); }} />
</View>
)
}
}
So basically in this example, once you click the 'doStuff' button, the request is immediately cancelled and you never get the 'done' alert. To be sure, it works, try and comment out these lines and click the button again:
alert('calling cancel');
this.controller.abort()
This time you will get the 'done' alert.
This is a simple example of hoe you can cancel a request using fetch in react native, feel free to adopt this to your own use case.
Here is a link to a demo on snackexpo https://snack.expo.io/#mazinoukah/fetch-cancel-request
hope it helps :)
the best solution is using rxjs observables + axios/fetch instead of promises, abort a request => unsubscribe an observable :
import Axios from "axios";
import {
Observable
} from "rxjs";
export default class HomeScreen extends React.Component {
subs = null;
doStuff = () => {
let observable$ = Observable.create(observer => {
Axios.get('https://jsonplaceholder.typicode.com/todos', {}, {})
.then(response => {
observer.next(response.data);
observer.complete();
})
});
this.subs = observable$.subscribe({
next: data => console.log('[data] => ', data),
complete: data => console.log('[complete]'),
});
}
cancel = () =>
if (this.subs) this.subs.unsubscribe()
componentWillUnmount() {
if (this.subs) this.subs.unsubscribe();
}
}
That is it :)
I have the following code which is not working to show an alert after my async function ends.
{
new BaseService().request(
serviceURL,
"POST",
headerParams,
bodyParams,
serverResponse => {
this.setState({ isLoading: true });
AuthenticationService.completeAuthentication(
serverResponse,
clientResponse => {
this.setState({ isLoading: false }); // THIS WORKS AND HIDES LOADER
alert("Authenticated Successfully!"); //THIS DOESN'T SHOW UP AN ALERT
},
error => {
alert(error);
}
);
}
)
}
Any leads?
Remember that not all browser functionalities will work in React Native, and that's the case for the alert API that you are using.
If you want a similar functionality, you should try with React Native's Alert component instead
Move the alert into the callback of setState
like bellow
clientResponse => {
this.setState({ isLoading: false },
() => alert("Authenticated Successfully!"));
},
I am running my test suit in this file :
import React from 'react'
import { PaymentDetails } from '../../../../app/screens/Account/components'
import { snapshot } from '../../../utils'
describe('PaymentDetails', () => {
const paymentMethod = {
accountName: 'Test Account',
expMonth: 11,
expYear: 2021,
number: '4111111111111111',
type: 'Visa',
preferred: true,
}
test('snapshot = paymentDetails not empty', () => {
snapshot(shallow(<PaymentDetails navigation={paymentMethod} />))
})
test('snapshot = paymentDetails empty', () => {
snapshot(shallow(<PaymentDetails navigation={{}} />))
})
I am getting this error:
Do I need to mock this library? I have tried that but not much help there.
Adding the file to moduleNameMapper in package.json fixed it for me.
"jest": {
"preset": "react-native",
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
},
"moduleNameMapper": {
"react-native-awesome-card-io": "<rootDir>/node_modules/react-native-awesome-card-io/CardIOView.ios.js"
}
If you would like all the modules to be stubbed, you could write a regex to match those file names.
jest.mock('react-native-awesome-card-io', () => {
return {
CardIOUtilities: {
preload: jest.fn(() => Promise.resolve('the response')),
},
}
})
This did the job for me. I did not need to map the module. So if you are mocking a native module make sure to use the original name for example CardIOUtility and not cardIOUtility alias.