skip to content

In React Native Jest tests, what is Platform.OS by default, and how do you test a component's Android branch?

level: juniorimportance: should knowfreq 38%

answer

  1. haste defaultPlatform in the preset
  2. Platform.ios.js is the one loaded
  3. select() is hard-wired per platform file
  4. import-time constants do not change
  5. a second Jest project for android

basics

~20 s

Platform.OS is 'ios' in tests because the React Native preset's haste defaultPlatform is 'ios', so Platform.ios.js and .ios files load. Test Android branches with a second Jest project whose defaultPlatform is 'android', or by injecting the value.

solid answer

~30 s

`@react-native/jest-preset` sets `haste.defaultPlatform` to `'ios'`, so Jest resolves `Platform.ios.js`, whose `OS` is `'ios'`, and prefers `Foo.ios.tsx` over `Foo.android.tsx`. Assigning `Platform.OS = 'android'` inside a test only changes code that reads `Platform.OS` at call time: `Platform.select` from `Platform.ios.js` still returns the `ios` key, platform files are already resolved, and constants computed at import keep their iOS value. The reliable way to test Android is a second Jest project with `haste.defaultPlatform: 'android'`, which loads `Platform.android.js` and `.android` files for the whole run. For one branch in one component, inject the platform value through a prop or a small module you can mock.

code

javascript · 14 lines
javascript
// jest.config.js
const base = { preset: '@react-native/jest-preset' };

module.exports = {
  projects: [
    { ...base, displayName: 'ios' },
    {
      ...base,
      displayName: 'android',
      haste: { defaultPlatform: 'android', platforms: ['android', 'ios', 'native'] },
      testMatch: ['<rootDir>/src/platform/**/*.test.tsx'],
    },
  ],
};

go deeper

for a junior

Recall that Platform.OS is 'ios' in React Native's default Jest setup and that Android branches need deliberate extra setup.

for a middle

Explain the preset's haste.defaultPlatform, why assigning Platform.OS misses select, platform files and import-time constants, and how to restore it.

for a senior

Set up an Android Jest project or platform injection so both branches run reliably, and scope it to code that actually branches.

for a principal

Decide how much per-platform logic belongs in shared code at all, since every branch doubles the tests needed to trust it.

## Where Platform.OS comes from in Jest In an app, `Platform` is resolved per platform by Metro: the Android bundle includes `Platform.android.js` and the iOS bundle includes `Platform.ios.js`. Each file exports an object whose `OS` field is a fixed string. Jest has no bundle target, so the React Native preset tells Jest's resolver which platform to prefer: ```js haste: { defaultPlatform: 'ios', platforms: ['android', 'ios', 'native'], }, ``` With `defaultPlatform: 'ios'`, every import of a module that has platform variants resolves the `.ios` file first. That includes React Native's own `Platform.ios.js` (so `Platform.OS === 'ios'`) and your app's own files such as `DatePickerField.ios.tsx`. ## Why assigning Platform.OS is only a partial fix `Platform` is a plain object, so a test can write `Platform.OS = 'android'`. That changes **some** behaviour and leaves the rest on iOS: | What the code does | Does `Platform.OS = 'android'` switch it? | Why | |---|---|---| | Reads `Platform.OS` inside a function at render time | Yes | The value is read after the assignment | | Calls `Platform.select({ ios, android })` | No | `Platform.ios.js` implements `select` by checking for the `ios` key, not by reading `OS` | | Imports `./Picker` with `Picker.ios.tsx` and `Picker.android.tsx` | No | The file was resolved to the `.ios` variant when the module loaded | | Computes a constant at module top level from `Platform.OS` | No | It ran at import, before the test's assignment | | Reads `Platform.Version` or other constants | No | They come from the iOS constants module, which the preset mocks | There is also a hygiene cost: the assignment mutates a shared object, so it must be restored afterwards or it leaks into later tests in the same file. ## Reliable ways to test the Android branch 1. **A second Jest project.** Jest's `projects` option runs the same tests under several configurations. A project that spreads the React Native preset and sets `haste.defaultPlatform: 'android'` loads `Platform.android.js` and every `.android` file, so `Platform.OS`, `Platform.select` and platform files all agree. The cost is running the chosen tests twice; many teams limit the Android project to the folders that branch on platform. 2. **Isolate modules per test.** Inside `jest.isolateModules`, change the platform and re-require the component so import-time code runs again. This works for `Platform.OS` reads but still cannot change how files resolve, so it is weaker than a project. 3. **Inject the platform.** Pass `platform` as a prop, or read it through a tiny module of your own (`getPlatform()`) that tests mock. This makes branch coverage explicit and keeps tests independent of React Native's resolution. ## What to test per platform, and what not to - Test **your** logic that differs per platform: which label, which component, which handler. - Do not try to test native platform behaviour in Jest, such as the Android back button closing a `Modal` or iOS keyboard avoidance; the preset mocks those components, so the test would only check the mock. - Keep the default iOS run as the main suite and add the Android project where branches exist, rather than doubling everything. ## Common mistakes in platform tests - **Asserting on the mock instead of the logic.** A snapshot that differs between the iOS and Android projects only because a mocked core component renders differently tells you nothing about your code. - **Mutating `Platform` in one test without restoring it.** Later tests in the file silently run the Android branch. - **Branching on `Platform.OS` deep inside many components.** Each branch needs its own test; pushing the decision into one module, or into platform files, keeps the number of branches small and testable. - **Expecting `Platform.Version` to match a real OS.** Under the preset it comes from mocked constants, so code that branches on OS version needs the value injected or mocked explicitly. ## Interview framing A junior answer says "`ios`, and I set `Platform.OS`". A stronger answer explains that the default comes from the preset's `haste` block, lists what the assignment does not change (`select`, resolved files, import-time constants) and proposes a second Jest project for real Android coverage.

  • A component renders DatePickerField, which has .ios.tsx and .android.tsx variants; which one does a default React Native Jest run render?
    The `.ios.tsx` variant. The preset's `haste.defaultPlatform` is `'ios'`, so Jest resolves the iOS file when the module loads, and assigning `Platform.OS` in the test afterwards cannot swap the already-resolved import. An Android Jest project, or importing the Android file directly in a dedicated test, covers the other variant.
  • Why must a test that assigns Platform.OS restore it?
    `Platform` is a shared module object within the test file. An assignment in one test stays in effect for every later test in that file, so a forgotten reset makes later tests run the Android branch unexpectedly. Save the original in `beforeEach` or restore it in `afterEach`.

saying these in an interview costs you the question

  • Platform.OS is undefined in Jest because there is no device
  • Setting Platform.OS = 'android' makes Platform.select return the android key
  • Jest loads Android and iOS platform files at random
  • Platform-specific .android.tsx files are picked up after assigning Platform.OS
  • The React Native preset defaults tests to the Android platform