In react-native we have .android.js
, .ios.js
, .web.js
, .windows.js
, etc. In order to test components on different platforms easily, it would be helpful to be able to specify what platform a specific test is for and have it automatically include the correct files based on the extension. As discussed on messenger @cpojer
To extend this issue:
There is not just the problem with platform specific files. If you have code like this:
import { StyleSheet, Platform } from 'react-native';
export default StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 30 : 10,
}
});
Platform.OS
is always 'ios' in jest tests.
Just to make sure it won't go unnoticed, Platform.select
has the same problem as Platform.OS
.
FWIW, I've been able to work around this by setting up jest mocks to point to the different files. It's pretty janky, so a built-in solution would be really nice :)
Something like:
jest.resetModules()
.doMock('./MyModule', () => {
return require.requireActual(`./MyModule.${Platform.OS}.js`);
});
Then in your test assign Platform.OS to your desired platform before requiring in your subject under test:
Platform.OS = 'android';
const subjectUnderTest = require('./subject-under-test');
Little bit late to the party but might be of value:
I have a 'general helpers' file in my app which exports an 'onAndroid' function with the implementation as such:
/* App/Lib/GeneralHelpers.js */
import { Platform } from 'react-native'
// Simple Platform boolean
export const onAndroid = () => Platform.OS === 'android'
This gives me the opportunity to easily make platform-specific code in components like this:
/* App/Components/randomComponent.js */
[...]
return(
<Text>
{ onAndroid() ? "android device" : "ios device" }
</Text>
)
and, why I'm typing this, to test components on a specific platform by mocking the 'onAndroid' function like so:
/* Tests/Components/randomComponent-test.js */
import * as helpers from '../../App/Lib/GeneralHelpers'
helpers.onAndroid = jest.fn(() => true)
[...]
// do some android specific testing
expect(TestRandomComponent.children[0]).toBe("android device")
helpers.onAndroid = jest.fn(() => false)
[...]
// do some iOS specific testing
expect(TestRandomComponent.children[0]).toBe("ios device")
Platform.select can be mocked to return android branch:
Platform.select = jest.fn(dict => dict.android);
Platform.OS can be specified easily by:
Platform.OS = 'android' or 'ios'
Anyone have any idea on how to deal with creating snapshots with platform-specific modules?
When testing a component that relies on TouchableNativeFeedback
for Android devices, setting Platform.OS
to android
for snapshot tests results in snapshots of error messages:
<View ...>
<Text ...>
TouchableNativeFeadback is not supported on this platform!
</Text>
</View>
I've been using this approach to switch to using an android version of Platform selet:
jest.mock('react-native', function() {
const reactNative = require.requireActual('react-native');
jest
.spyOn(reactNative.Platform, 'select')
.mockImplementation(obj => obj.android || obj.default);
return reactNative;
});
Not sure if it's the best approach
Any update on this? I can't find an elegant approach to solve this :(
@gogumai I did this on my code and it actually works.
...
it('should render picker on Android only with required props', () => {
jest.mock('Platform', () => ({
OS: 'android',
}));
let component = renderer.create(<Picker options={OPTIONS} />);
expect(component.toJSON()).toMatchSnapshot();
});
...
I'm using Jest v21.2.1.
@gogumai After spending many hours, found this solution:
I moved the jest config from package.json
to separate files.
So far everything seems to work great, including:
a) the right file is imported according to the platform. For example on ios: .ios.tsx, then .native.tsx then .tsx
b) PLATFORM.IOS returns true when running test-ios, no need to mock anything
// package.json
"scripts": {
"test": "cross-env NODE_ENV=test jest --config config/jest.desktop.json",
"test-ios": "cross-env NODE_ENV=test jest --config config/jest.ios.json",
"test-android": "cross-env NODE_ENV=test jest --config config/jest.android.json"
}
// config/jest.web.json
{
...
}
// config/jest.ios.json
{
...
"preset": "react-native",
"haste": {
"defaultPlatform": "ios",
"platforms": [
"android",
"ios",
"native"
],
"providesModuleNodeModules": [
"react-native"
]
},
}
// config/jest.android.json
{
...
"preset": "react-native",
"haste": {
"defaultPlatform": "android",
"platforms": [
"android",
"ios",
"native"
],
"providesModuleNodeModules": [
"react-native"
]
},
}
Hey @grigored, that solution from #4455 is a step in the right direction, but any idea how to use snapshots when you want to test on multiple platforms? When testing on one platform, the snapshots don't match up with the output from the other platform.
Per #1650, there seems to be no way to configure Android snapshots to go in one place while iOS snapshots go to another.
Hm... I guess you could write a short bash script to save the snapshots in __snapshots_ios__ after the test is run, and back when you run the tests. something like this might work
// package.json
"scripts": {
"test-ios": "find __tests__ -type d -name '__snapshots_ios__' -print0 | xargs -0 -I {} mv {} {}/../__snapshots__ && cross-env NODE_ENV=test jest --config config/jest.ios.json && find __tests__ -type d -name '__snapshots__' -print0 | xargs -0 -I {} mv {} {}/../__snapshots_ios__"
}
Any latest solution to this?
No other than setting projects
config separately for iOS/Android/other platforms.
Non of the above worked for me.
I have outlined a solution below that works for me.
// styles.js
// Styled component.
import styled from "styled-components";
export const Heading = styled.View`
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
padding-top: ${props => props.platform && props.platform === 'ios' ? 30 : 20};
padding-left: 28;
padding-right: 28;
min-height: 70;
`;
// styles.test.js
// Tests.
import React from "react";
import { shallow } from "enzyme";
import toJson from "enzyme-to-json";
import { Heading } from "../styles";
describe("Heading Component", () => {
it("Should render without issues", () => {
const component = shallow(<Heading/>);
expect(component.length).toBe(1);
expect(toJson(component)).toMatchSnapshot();
});
it("Should render without issues on ios", () => {
const component = shallow(<Heading platform='ios'/>);
expect(component.length).toBe(1);
expect(toJson(component)).toMatchSnapshot();
});
it("Should render without issues on android", () => {
const component = shallow(<Heading platform='android'/>);
expect(component.length).toBe(1);
expect(toJson(component)).toMatchSnapshot();
});
});
// CommonHeader/index.js
// Usage example.
import PropTypes from "prop-types";
import React from "react";
import styled from "styled-components";
import { Platform } from 'react-native';
import { BackButton } from "../BackButton";
import { CloseButton } from "../CloseButton";
import { Heading } from "./styles";
export class CommonHeader extends React.Component {
render() {
const { componentId } = this.props;
const logo = this.props.logo
? this.props.logo
: require("@/images/Logos/rouge.png");
return (
<Heading platform={Platform.OS}>
<BackButton componentId={componentId} />
<LogoContainer>
<Logo source={logo} />
</LogoContainer>
<CloseButton componentId={componentId} />
</Heading>
);
}
}
CommonHeader.propTypes = {
componentId: PropTypes.string.isRequired
};
const LogoContainer = styled.View`
flex-direction: column;
justify-content: flex-start;
`;
const Logo = styled.Image``;
Hi !
Any update on this? I've started a project two weeks ago with an updated react-native and jest version and the issue is still present.
Looks like https://jestjs.io/docs/en/configuration.html#snapshotresolver-string could be leveraged to store the snapshots in different paths based on the config.
In react-native we use Platform check a lot and it's a shame that there is no easy way to mock Platform to our tests.
Currently, the best way to do this is by using @expo/universal approach from Expo team.
If you use Expo you could use @expo/universal directly but if you use bare react-native, you can copy the approach and edit it to work with your project.
Little bit late to the party but might be of value:
I have a 'general helpers' file in my app which exports an 'onAndroid' function with the implementation as such:
/* App/Lib/GeneralHelpers.js */ import { Platform } from 'react-native' // Simple Platform boolean export const onAndroid = () => Platform.OS === 'android'
This gives me the opportunity to easily make platform-specific code in components like this:
/* App/Components/randomComponent.js */ [...] return( <Text> { onAndroid() ? "android device" : "ios device" } </Text> )
and, why I'm typing this, to test components on a specific platform by mocking the 'onAndroid' function like so:
/* Tests/Components/randomComponent-test.js */ import * as helpers from '../../App/Lib/GeneralHelpers' helpers.onAndroid = jest.fn(() => true) [...] // do some android specific testing expect(TestRandomComponent.children[0]).toBe("android device") helpers.onAndroid = jest.fn(() => false) [...] // do some iOS specific testing expect(TestRandomComponent.children[0]).toBe("ios device")
Absolutely brilliant approach works wonderfully.
Bumped on the same problem. It seems like haste
for now is the only way to solve this.
Created a dummy test project to have a possibility to run 2 set of tests (iOS and Android):
i have the same issue too.
No other than setting
projects
config separately for iOS/Android/other platforms.
I would like to do snapshot tests for both iOS and Android, so the haste settings in the config file are not working for me.
@thymikee Any other working solution for snapshot tests? Thanks.
Most helpful comment
Little bit late to the party but might be of value:
I have a 'general helpers' file in my app which exports an 'onAndroid' function with the implementation as such:
This gives me the opportunity to easily make platform-specific code in components like this:
and, why I'm typing this, to test components on a specific platform by mocking the 'onAndroid' function like so: