Stuart Watt
Stuart Watt

Reputation: 5401

Mocking platform detection in Jest and React Native

Some of the code I am trying to test detects the platform, using, e.g.:

import { Platform } from 'react-native';
...

if (Platform.OS === 'android') {
  ...
} else {
  ...
}

Is there a sensible way to mock this with Jest and/or something else, so I can test both branches in one test run?

Or is the smart way to decouple it and put the platform into, e.g., a context variable? Although it always feels restructuring code to make it easier to test is something of a cheat.

Upvotes: 55

Views: 46392

Answers (21)

Nagibaba
Nagibaba

Reputation: 5358

This works very well as intended:

import { Platform } from 'react-native'

jest.doMock('react-native/Libraries/Utilities/Platform.android.js', () => ({
  OS: 'android',
  select: jest.fn(),
}))

jest.doMock('react-native/Libraries/Utilities/Platform.ios.js', () => ({
  OS: 'android',
  select: jest.fn(),
}))


it('ios', () => {
  Platform.OS = 'ios'
  ...
})

it('android', () => {
  Platform.OS = 'android'
  ...
})

Upvotes: 0

You can mock the Platform object using jest.replaceProperty. For more info you can visit https://jestjs.io/docs/jest-object#jestreplacepropertyobject-propertykey-value

import { Platform } from "react-native";

        
it("ios test", async () => {
    
  jest.replaceProperty(Platform, "OS", "ios");
        
  // Your test
});

Upvotes: -1

Luciano Corniglione
Luciano Corniglione

Reputation: 511

For everyone looking for this, what it helped me was the following:

jest.mock('react-native/Libraries/Utilities/Platform', () => ({
    OS: 'android', // or 'ios'
    select: () => null
}));

Upvotes: 39

tany4
tany4

Reputation: 194

React Native 0.61 update

Though the accepted solution works for versions of React Native 0.60 and below, React Native 0.61 has dropped Haste support and this gives an error.

I was able to mock platform detection following the implementation described in this blog post.

Practically, according to the React team, we now have to mock the react-native interface. So, you can create a react-native.js file inside the tests/__mocks__ folder and add this code to mock Platform:

import * as ReactNative from "react-native";

export const Platform = {
  ...ReactNative.Platform,
  OS: "ios",
  Version: 123,
  isTesting: true,
  select: objs => objs[Platform.OS]
};

export default Object.setPrototypeOf(
  {
    Platform
  },
  ReactNative
);

With this implementation, we can now simply overwrite the OS before running the test like:

Platform.OS = 'android'

Upvotes: 10

NiFi
NiFi

Reputation: 2458

To change Platform only for a specific test, the following can be used:

  test('Platform should be Android', () => {
    jest.doMock('react-native/Libraries/Utilities/Platform', () => ({
      OS: 'android',
    }));

    expect(Platform.OS).toBe('android');

    // restore the previous value 'ios' for Platform.OS
    jest.dontMock('react-native/Libraries/Utilities/Platform');
  });

Upvotes: 1

Oskar
Oskar

Reputation: 1618

I implemented a small mock that allows you to change Platform during tests in the same test file.

Add this to your jest setup file

jest.mock('react-native/Libraries/Utilities/Platform', () => {
  let platform = {
    OS: 'ios',
  }

  const select = jest.fn().mockImplementation((obj) => {
    const value = obj[platform.OS]
    return !value ? obj.default : value
  })

  platform.select = select

  return platform
});

Then you can easily change Platform in your test. If you are using Platform.select it will also work as expected!

import { Platform } from 'react-native'

describe('When Android', () => {
  it('should ...', () => {
    Platform.OS = 'android'
    ...
  })
})

describe('When iOS', () => {
  it('should ...', () => {
    Platform.OS = 'ios'
    ...
  })
})

Upvotes: 12

Francis Leigh
Francis Leigh

Reputation: 1960

jest: ^26.5.3

See bottom of this article

import { Platform } from 'react-native';

describe('Android', () => {
  it('renders Element if Android', () => {
    Platform.OS = 'android';
    renderIfAndroid();
    expect(wrapper.find(Element)).exists()).toBe(true);
  });
});

describe('IOS', () => {
  it('renders Element if IOS', () => {
    Platform.OS = 'ios';
    renderIfIOS();
    expect(wrapper.find(Element)).exists()).toBe(true);
  });
});

Upvotes: 2

Maheshvirus
Maheshvirus

Reputation: 7523

If anyone is looking out to mock Platform.select. The below code can fix your issue.

const mockedData = 'MOCKED-DATA'
jest.mock('react-native', () => ({

    Platform: {
        select: jest.fn(() => {
            return { mockedData  } // Your Mocked Value
        }),
    }
}));

And To mock both OS and Platform. Please refer below code.

 jest.mock('Platform', () => ({
      OS: 'android', // or 'ios'
      select: () => 'mocked-value'
 }));

Upvotes: 0

Surender Kumar
Surender Kumar

Reputation: 1285

for newer version

"react-native": "0.62.2"

"enzyme": "^3.11.0"

"jest": "24.5.0"

Put it at the top of your test

Object.defineProperty(Platform, 'OS', { get: jest.fn(() => 'ios') })

Upvotes: 2

Simon Gamble
Simon Gamble

Reputation: 61

This works for me...

jest.mock('react-native/Libraries/Utilities/Platform', () => {
  const Platform = require.requireActual(
    'react-native/Libraries/Utilities/Platform'
  )
  Platform.OS = 'android'
  return Platform
})

Upvotes: 6

d0gb3r7
d0gb3r7

Reputation: 842

Since the other answers will not work if you want to mock different OSs in the same test suite and in one test run, here's another way. Instead of using Platform.OS directly in your code, define a helper function somewhere and use that to get references to the OS in your components:

in 'helpers.js':

export function getOS() {
  return Platform.OS;
}

in your component:

import * as helpers from './helpers';
render() {
    if (helpers.getOS() === 'android') {// do something}
}

This function can then be mocked it in your tests, e.g.

import * as helpers from './helpers';

// ...

it('does something on Android', () => {
  jest.spyOn(helpers, 'getOS').mockImplementation(() => 'android');
  // ...
}

it('does something else on iOS', () => {
  jest.spyOn(helpers, 'getOS').mockImplementation(() => 'ios');
  // ...
}

Credit for the idea goes to this GitHub issue comment.

Upvotes: 7

Estevão Lucas
Estevão Lucas

Reputation: 4678

this is the mock you need:

const mockPlatform = OS => {    
  jest.resetModules();  
  jest.doMock("Platform", () => ({ OS, select: objs => objs[OS] }));
};

with it you can do the following:

it("my test on Android", () => {
  mockPlatform("android");
});

it("my test on iOS", () => {
  mockPlatform("ios");
});

That way you can have tests for both platforms

Upvotes: 5

ethanneff
ethanneff

Reputation: 3441

import React from "react";
import renderer from "react-test-renderer";
import SmartText from "../SmartText";

describe("markdown smart text component", () => {
  beforeEach(() => {
    jest.resetModules();
  });

  it("renders with props on ios", () => {
    jest.mock("Platform", () => {
      return { OS: "ios" };
    });
    expect(
      renderer.create(<SmartText title="code ios" code />).toJSON()
    ).toMatchSnapshot();
  });

  it("renders with props on android", () => {
    jest.mock("Platform", () => {
      return { OS: "android" };
    });
    expect(
      renderer.create(<SmartText title="code android" code />).toJSON()
    ).toMatchSnapshot();
  });
});

Upvotes: 1

user10124150
user10124150

Reputation:

Maybe the problem in the "import" method, check this:

const isAndroid = require('app/helpers/is_android');

//import isAndroid from 'app/helpers/is_android'

with "import" this will not work, need to use "require".

beforeEach(() => {
  jest.resetModules();
});

it("should be true when Android", () => {
  jest.mock('Platform', () => {
    return { OS: 'android' };
  });

  expect(isAndroid).toBe(true);
});   

Upvotes: 1

user3526468
user3526468

Reputation: 354

I'm using the solution from this github issue https://github.com/facebook/jest/issues/1370#issuecomment-352597475

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"
    ]
  },
}

Upvotes: 3

acharlop
acharlop

Reputation: 407

OS can be set directly for each test

 test('android', () => {
     Platform.OS = 'android'
     const component = renderer.create(<Component />).toJSON()
     expect(component).toMatchSnapshot()
 })
 test('ios', () => {
     Platform.OS = 'ios'
     const component = renderer.create(<Component />).toJSON()
     expect(component).toMatchSnapshot()
 })

Upvotes: -2

Andreas K&#246;berle
Andreas K&#246;berle

Reputation: 110922

You have to mock the module and import it into your test. Then you can use mockImplementation to set the it to either android or ios

import reactNative from 'react-native';
jest.mock('react-native', () = > jest.fn();

it('is android', () => {
  reactNative.mockImplementation(()=>({Platform:{OS: 'android'}}))
  //test the android case
})

it('is android', ()=>{
  reactNative.mockImplementation(()=>({Platform: { OS: 'io' }}))
  //test the ios case
})

Upvotes: -2

gran33
gran33

Reputation: 12951

You can mock whatever you want from React-Native like this:

describe('notifications actions tests', () => {
  let Platform;


  beforeEach(() => {
    jest.mock('react-native', () => ({
          Platform: {
           ... 
        }));


    Platform = require('react-native').Platform; // incase u would like to refer to Platform in your tests
  });

Upvotes: 0

jkvim
jkvim

Reputation: 31

use jest.doMock and jest.resetModules

jest.resetModules()
jest.doMock('react-native', () => ({ Platform: { OS: 'android' }}))

Upvotes: 2

Boomer Rogers
Boomer Rogers

Reputation: 1015

The way that I achieved mocking setting the platform was just set it directly in the tests:

it('should only run for Android', () => {
  Platform.OS = 'android'; // or 'ios'

  // For my use case this module was failing on iOS
  NativeModules.MyAndroidOnlyModule = {
    fetch: jest.fn(
      (url, event) => Promise.resolve(JSON.stringify(event.body))
    ),
  }; 
  return myParentFunction().then(() => {
    expect(NativeModules.MyAndroidOnlyModule.fetch.mock.calls.length).toBe(1);
    expect(fetch.mock.calls.length).toBe(0);
  });
});

This would setup the platform to only run on Android during tests to make sure that my function was calling only specific functions. My function that was wrapped in platform dependent compilation looked like:

export default function myParentFunction() {
  if (Platform.OS === 'ios') {
    return fetch();
  }
  return NativeModules.MyAndroidOnlyModule.fetch();
}

I would suggest just creating two different tests one with the platform set to iOS and the other to Android since ideally a function should only have one responsibility. However, I'm sure you can use this to run the first test, dynamically set the platform and run test number two all in one function.

Upvotes: 15

Bramus
Bramus

Reputation: 2059

This worked for me (Jest 21.2.1, Enzyme 3.2.0):

jest.mock('Platform', () => {
    const Platform = require.requireActual('Platform');
    Platform.OS = 'android';
    return Platform;
});

Put it either at the top of your test, or in a beforeAll for example.

Upvotes: 36

Related Questions