Jest 和 React Native 中的模拟平台检测

Posted

技术标签:

【中文标题】Jest 和 React Native 中的模拟平台检测【英文标题】:Mocking platform detection in Jest and React Native 【发布时间】:2017-08-26 22:47:09 【问题描述】:

我尝试测试的一些代码可以检测平台,例如:

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

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

有没有一种明智的方法可以用 Jest 和/或其他东西来模拟这个,所以我可以在一次测试运行中测试两个分支?

或者是将平台解耦并将平台放入例如上下文变量的智能方法?尽管总觉得重组代码以使其更容易测试是一种欺骗。

【问题讨论】:

【参考方案1】:

你可以像这样从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
  );

【讨论】:

这不是永久的吗?即,这是否意味着我需要在afterEach 中“取消模拟”?这可能吗? 嗯,这行不通。 jest.mock 被提升所以它不在beforeEach 中,所以它在一切之前运行。一个可行的选择似乎是在测试之前猴子修补RN.Platform.OS 对象。或者将平台解耦到上下文中。【参考方案2】:

您必须模拟模块并将其导入到您的测试中。然后您可以使用mockImplementation 将其设置为androidios

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
)

【讨论】:

我试过这个方法,但我得到了_reactNative2.default.mockImplementation is not a function。你有什么主意吗?谢谢。 这行不通,因为它被定义为ReactNative.Platform.OS,而不仅仅是ReactNative.Platform 还是不行_reactNative2.default.mockImplementation is not a function【参考方案3】:

我实现模拟设置平台的方式是直接在测试中设置:

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,因为理想情况下,一个函数应该只负责一项职责。不过,我相信您可以使用它来运行第一个测试、动态设置平台并在一个函数中运行第二个测试。

【讨论】:

这种方法不会改变所有测试的 Platform.OS 吗? 我想你可以把它设置回去,const previousPlatform = Platform.OS;测试结束时 Platform.OS = previousPlatform;【参考方案4】:

使用jest.doMock 和jest.resetModules

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

【讨论】:

【参考方案5】:

可以为每个测试直接设置操作系统

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

【讨论】:

没有解释的否决票对任何人都没有帮助。请解释 测试通过,但从测试覆盖率(分支列)看来,这两个平台都没有测试【参考方案6】:

这对我有用(Jest 21.2.1,Enzyme 3.2.0)

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

例如,将其放在测试的顶部,或者放在 a beforeAll 中。

【讨论】:

任何有流量问题的人:使用jest.requireActual而不是require.requireActual 模拟的缺点是您只能针对一个特定平台测试您的应用程序。 iOS 或安卓。但是,如果您想测试特定组件在每个平台上的行为方式怎么办? Object.defineProperty(Platform, 'OS', get: jest.fn(() =&gt; 'ios') ) )beforeAll 声明时有效 @Andru 我修改了代码(所以它不再在 beforeAll 中),但这对我有用 codepile.net/pile/Yr5DkK4n 这个解决方案在 react-native 0.61 之后不再起作用。请参阅 Simon Gamble 的答案或这些链接 github.com/facebook/react-native/issues/… altany.github.io/react-native/0.61/jest/mocking/upgrade/2020/01/… github.com/facebook/react-native/issues/…【参考方案7】:

我正在使用来自这个 github 问题 https://github.com/facebook/jest/issues/1370#issuecomment-352597475 的解决方案

我将 jest 配置从 package.json 移动到单独的文件中。 到目前为止,一切似乎都很好,包括: a) 根据平台导入正确的文件。例如在 ios 上:.ios.tsx,然后是 .native.tsx,然后是 .tsx b) PLATFORM.IOS 在运行 test-ios 时返回 true,不需要 mock 任何东西

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

【讨论】:

【参考方案8】:

如果您想在同一个测试套件中模拟不同的操作系统和在一个测试运行中,其他答案将不起作用,这是另一种方法。不要在代码中直接使用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');
  // ...

这个想法的功劳归于this GitHub issue comment。

【讨论】:

对我不起作用。我收到错误"getOS" is read-only.mockImplementationOnce @Andru 感谢通知,这已经过时了。刚刚将其更新为目前对我有用的内容(RN 0.59,Jest 24)。 感谢@d0gb3r7 如此迅速地调整它! 在 Python 中模拟 platform.system() 之类的东西(使用任何测试运行程序)是微不足道的。玩笑(使用打字稿),经过几个小时的模拟*实用程序大军尝试,我最终用这里提到的方法模拟了os.homedir()。我可能缺乏 Jest 和 JS/TS 测试的经验,但是提供的选项数量(只是暂时从 stdlib 模块中切换出一个名称),其中几乎没有一个工作简单/直观/如预期的那样有点令人惊讶和相当糟糕的体验。【参考方案9】:

可能是“导入”方法的问题,检查一下:

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

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

用“import”这个不行,需要用“require”。

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

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

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

【讨论】:

【参考方案10】:
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();
  );
);

【讨论】:

【参考方案11】:

这是你需要的模拟:

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

这样您就可以对两个平台进行测试

【讨论】:

对我不起作用。平台不会改变默认的iOS【参考方案12】:

对于寻找这个的每个人来说,它对我的​​帮助如下:

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

【讨论】:

谢谢你,我没想到要检查平台实用程序,但找不到。对于任何试图模拟这些模块的人,请注意,由于移除了 Haste,因此在 >0.61 中需要模拟模块的完整路径。 这对我不起作用,但请查看我的答案,了解我如何使用 0.61 模拟平台检测。 谢谢!我可以确认这适用于 react-native 0.64.3【参考方案13】:

React Native 0.61 更新

虽然公认的解决方案适用于 React Native 0.60 及以下版本,但 React Native 0.61 已放弃对 Haste 的支持,这会产生错误。

我能够按照blog post 中描述的实现模拟平台检测。

实际上,according to the React team,我们现在必须模拟 react-native 接口。因此,您可以在 tests/__mocks__ 文件夹中创建一个 react-native.js 文件并将此代码添加到模拟平台:

import * as ReactNative from "react-native";

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

export default Object.setPrototypeOf(
  
    Platform
  ,
  ReactNative
);

有了这个实现,我们现在可以在运行测试之前简单地覆盖操作系统:

Platform.OS = 'android'

【讨论】:

【参考方案14】:

这对我有用...

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

【讨论】:

【参考方案15】:

更新版本

“反应原生”:“0.62.2”

“酶”:“^3.11.0”

“开玩笑”:“24.5.0”

把它放在测试的顶部

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

【讨论】:

【参考方案16】:

如果有人想模拟Platform.select。下面的代码可以解决你的问题。

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

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

并且模拟OSPlatform。请参考以下代码。

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

【讨论】:

【参考方案17】:

jest: ^26.5.3

见底部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);
  );
);

【讨论】:

【参考方案18】:

我实现了一个小模拟,允许您在同一测试文件中的测试期间更改 Platform

将此添加到您的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
);

然后您可以在测试中轻松更改Platform。如果您使用Platform.select,它也将按预期工作!

import  Platform  from 'react-native'

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

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

【讨论】:

以上是关于Jest 和 React Native 中的模拟平台检测的主要内容,如果未能解决你的问题,请参考以下文章

如何在带有 Jest 的 react-native 中使用模拟的 fetch() 对 API 调用进行单元测试

NativeBase 内容未在 Jest 中使用 react-native-testing-library 呈现

React Native - JEST:没有找到 haste.hasteImplModulePath 选项中的 react-native/jest/hasteImpl.js

如何正确模拟使用本机代码的 React Native 模块?

意外的导入令牌 - 使用 Jest 测试 React Native

使用 Jest-React Native 测试 fetch