Обманивание платформы в Jest и React Native

Некоторые из кода, который я пытаюсь проверить, обнаруживают платформу, используя, например:

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

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

Есть ли разумный способ издеваться над этим с Jest и/или чем-то еще, поэтому я могу проверить обе ветки в одном тестовом прогоне?

Или это умный способ разделить его и включить платформу, например, в контекстную переменную? Хотя он всегда чувствует, что код реструктуризации, чтобы облегчить тестирование, является чем-то вроде обмана.

Ответ 1

Это сработало для меня (Jest 21.2.1, Enzyme 3.2.0):

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

Поместите его либо в начало вашего теста, либо в beforeAll например.

Ответ 2

То, как я достиг высмеивания настроек платформы, просто установил его непосредственно в тестах:

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);
  });
});

Это позволит настроить платформу только на Android во время тестов, чтобы убедиться, что моя функция вызывает только определенные функции. Моя функция, которая была завернута в компиляцию, зависящую от платформы, выглядела так:

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

Я бы предложил просто создать два разных теста: один с платформой, установленной для iOS, а другой для Android, поскольку в идеале функция должна иметь только одну ответственность. Однако я уверен, что вы можете использовать это для запуска первого теста, динамически установить платформу и запустить тестовый номер два в одной функции.

Ответ 3

Поскольку другие ответы не будут работать, если вы хотите смоделировать разные ОС в одном и том же наборе тестов и в одном тестовом прогоне, здесь другой способ. Вместо того, чтобы использовать Platform.OS непосредственно в своем коде, определите где-нибудь вспомогательную функцию и используйте ее для получения ссылок на ОС в ваших компонентах:

в 'helpers.js':

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

в вашем компоненте:

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

Затем эту функцию можно смоделировать в ваших тестах, например

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');
  // ...
}

Автор идеи - это комментарий к проблеме GitHub.

Ответ 4

это макет, который вам нужен:

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

с его помощью вы можете сделать следующее:

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

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

Таким образом, вы можете иметь тесты для обеих платформ

Ответ 5

использовать jest.doMock и jest.resetModules

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

Ответ 6

Я использую решение этой проблемы github https://github.com/facebook/jest/issues/1370#issuecomment-352597475

Я переместил jest config из package.json для разделения файлов. Пока все работает отлично, в том числе: а) правый файл импортируется в соответствии с платформой. Например, на ios:.ios.tsx, тогда.native.tsx then.tsx b) PLATFORM.IOS возвращает true при запуске test-ios, не нужно ничего издеваться

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

Ответ 7

Возможно, проблема в методе "импорт", проверьте это:

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

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

с "импортом" это не сработает, нужно использовать "require".

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

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

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

Ответ 8

Вы можете издеваться над тем, что хотите от React-Native как это:

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
  });

Ответ 9

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();
  });
});

Ответ 10

Я использовал beforeAll() и afterAll() чтобы переопределить настройку реагирования на beforeAll() afterAll() а затем установил его обратно, чтобы не нарушать последующие тесты.

describe('#iOS tests', () => {

  beforeAll(function(){
    this.os_ = Platform.OS
    Platform.OS = 'ios'
  });

  afterAll(function(){
    // Clear up my override when this group of tests have finished
    Platform.OS = this.os_
  });

  it('should run my test here...', () => {
     // ... my iOS specific test
  })
})

Ответ 11

OS может быть настроена непосредственно для каждого теста

 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()
 })

Ответ 12

Вы должны издеваться над модулем и импортировать его в свой тест. Затем вы можете использовать mockImplementation, чтобы установить его на любой android или 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
})