RealOrangeOne / react-native-mock

A fully mocked and test-friendly version of react native (maintainers wanted)
MIT License
571 stars 153 forks source link

Mocking platform-agnostic requires #17

Open IanVS opened 8 years ago

IanVS commented 8 years ago

React Native does some magic around requires to allow require(img.png) as pointed out in https://github.com/lelandrichardson/react-native-mock/issues/11, but it also allows platform-specific components to be named with an extension .ios.js or .android.js, and then be imported with a simple import {MyComponent} from 'my-component.js'; (http://facebook.github.io/react-native/docs/platform-specific-code.html#platform-specific-extensions). This avoids the need to sprinkle Platform.OS ternaries throughout the code, but of course Node has no idea how to resolve the import/require and blows up.

Do you have any suggestions or ideas of the best way to handle this?

Jeiwan commented 8 years ago

I use an environment variable to set the platform, like APP_PLATFORM=android npm test. And in the code I do var platform = process.env.APP_PLATFORM || 'ios' and then use this variable during mocking a require or doing other platform-specific stuff. Another option is to have separate tests for each platform, but this leads to code doubling. Not sure which of these approaches is better.

React Native uses packager that dynamically builds js-bundles depending on provided parameters, like platform, dev, hot, that's why it's so flexible and power.

RealOrangeOne commented 8 years ago

Besides just using a load of conditionals to check, what you could do is change the way node requires js files, and check if there's a .android.js or .ios.js version and return that instead, which you could switch on an env var. Best library i've found to do it is node-hook, but I have no idea if it actually works, or plays nice with react-native-mock

julien-rodrigues commented 8 years ago

I just want to share something in case someone come through the problem of testing platform specific code.

Not related to the import themselves but I found a solution for platform specific code using the RN Platform module. I use mocha and as entry point for my tests I require this file /bin/test.js:

var ReactNativeMock = require('react-native-mock');

ReactNativeMock.Platform.OS = process.env.PLATFORM || 'ios';

require('react-native-mock/mock');

This way I don't have to change my code, I keep using the RN module and I can simulate the Platform with an env var. Unfortunately this doesn't work for imports as they are handled by the RN packager.

RealOrangeOne commented 8 years ago

@julien-rodrigues whilstt that is a nice solution, we already support something like this. You can use 'Platform.__setOS' to set the OS programatically during tests. This should make things much easier for you, and it's still possible to use process.env.

julien-rodrigues commented 8 years ago

I forgot to say that in my case I need to set stuffs outside of the render cycle. So I need to have the OS correctly set before anything else.

I am testing a component which style has a color property which is changing depending on the Platform.OS value.

export const selectionColor = Platform.OS === 'ios' ? Colors.lighterGrey : Colors.lighterGrey20;

I can't put it inside a RN Stylesheet as selectionColor is going to be used to fill the selectionColor prop of the RN TextInput component.

I could put the ternary inside the selectionColor prop of the TextInput and this would allow me to change the OS programatically in my tests as the check is done inside the render cycle. But I don't see the case where the Platform would change its OS when running the app. And for the sake of consistancy, all my component styles are in a separate file.

But wrapping it into a function would be a working solution tho. lol

And as for the __setOS method, seeing the __ made me think that I shouldn't be using that. But knowing that I can I'm going to change my test's entry point. Thanks for the hint!

danielbh commented 7 years ago

Another way to do it is to do it is by component basis. The issue with this is that you need to include the a boolean for Platform as a prop. While it adds a bit extra code . Its allows me to mock things in a contained manner.

Here is a webView component below as an example. I use it to conditionally create a vertical offset due to a navigation bar component. Would love to hear your feedback!

const SHWebView = ({ verticalOffset, uri, android }) => {
  const androidOffset = 53;
  const iosOffset = 64;
  const marginTop = 
    isNaN(verticalOffset) ? (android ? androidOffset: iosOffset) : verticalOffset;

  return (
    <View style={[styles.container, { marginTop }]}>
      <WebView source={{ uri }}/>
    </View>
  )
};

SHWebView.defaultProps = {
  android: Platform === 'android'
}
SandroMachado commented 7 years ago

@RealOrangeOne I am trying to set the platform to Android, using:Platform.__setOS('android');

Unfortunately it is not working, the component does not render. Is there another way to do it?

If I do Platform.__setOS('ios'); it does what is expected that is keep the iOS platform.

RealOrangeOne commented 7 years ago

@SandroMachado I'm going to need a lot more information than that. You have to re render you component completely after changing the OS. We also currently dont support using platforms in require statements, if that's what you meant

danielbh commented 7 years ago

Just pass it in as a default boolean prop!

Component.defaultProps { android: Platform.OS === 'android' }

Does that work for you?

On 29 Nov 2016, at 21:42, Jake Howard notifications@github.com<mailto:notifications@github.com> wrote:

@SandroMachadohttps://github.com/SandroMachado I'm going to need a lot more information than that. You have to re render you component completely after changing the OS. We also currently dont support using platforms in require statements, if that's what you meant

- You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/RealOrangeOne/react-native-mock/issues/17#issuecomment-263692513, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AGWFMh2VPbd5ZDHmlON8Q_PY09j-1Ximks5rDI4sgaJpZM4Hnv1G.

SandroMachado commented 7 years ago

I think I found the issue, the problem is that the DatePickerAndroid is not mocked.

On 29 Nov 2016 20:50, "Daniel Hollcraft" notifications@github.com wrote:

Just pass it in as a default boolean prop!

Component.defaultProps { android: Platform.OS === 'android' }

Does that work for you?

On 29 Nov 2016, at 21:42, Jake Howard <notifications@github.com<mailto: notifications@github.com>> wrote:

@SandroMachadohttps://github.com/SandroMachado I'm going to need a lot more information than that. You have to re render you component completely after changing the OS. We also currently dont support using platforms in require statements, if that's what you meant

- You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/ RealOrangeOne/react-native-mock/issues/17#issuecomment-263692513, or mute the threadhttps://github.com/notifications/unsubscribe- auth/AGWFMh2VPbd5ZDHmlON8Q_PY09j-1Ximks5rDI4sgaJpZM4Hnv1G.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/RealOrangeOne/react-native-mock/issues/17#issuecomment-263694708, or mute the thread https://github.com/notifications/unsubscribe-auth/AAmju3Yhh0prHjt9eZhCbCIMMWVC_PLhks5rDJAugaJpZM4Hnv1G .

esauter5 commented 6 years ago

Curious if anyone reached a good solution for the import issue? Currently, I'm creating a .js file at test runtime and then deleting it after in my script. Talk about a hack 😝