React-Native 应用程序的 Jest 测试 Animated.View

Posted

技术标签:

【中文标题】React-Native 应用程序的 Jest 测试 Animated.View【英文标题】:Jest test Animated.View for React-Native app 【发布时间】:2017-07-05 06:04:00 【问题描述】:

我尝试使用 Jest 为 React-Native 测试 Animated.View。当我将属性visible 设置为true 时,它​​应该将我的视图从opacity 0 设置为opacity 1

这是我的组件呈现的内容:

<Animated.View
    style=
        opacity: opacityValue,
    
>
    <Text>message</Text>
</Animated.View>

当道具visible 更改时,opacityValue 会更新:

Animated.timing(
    this.opacityValue, 
        toValue: this.props.visible ? 1 : 0,
        duration: 350,
    ,
).start(),

我想确保我的视图在我将其设置为属性visible=true 时可见。尽管视图需要一些时间才能变得可见,并且随着测试的运行,不透明度等于0

这是我的测试吧:

it('Becomes visible when visible=true', () => 
    const tree = renderer.create(
        <MessageBar
            visible=true
        />
    ).toJSON();
    expect(tree).toMatchSnapshot();
);

我想知道如何让 Jest 等待?或者我如何测试它以确保当我将道具设置为 true 时视图变得可见?

谢谢。

【问题讨论】:

【参考方案1】:

我通过为测试创建一个动画存根解决了这个问题。

我看到您使用 visible 作为属性,所以一个工作示例是:

组件代码

import React from 'react';                                                                                                                                                                            
import  Animated, Text, View, TouchableOpacity  from 'react-native';                                                                                                                                

// This class will control the visible prop                                                                                                                                                                                                  
class AnimatedOpacityController extends React.Component                                                                                                                                              

  constructor(props, ctx)                                                                                                                                                                            
    super(props, ctx);                                                                                                                                                                                
    this.state =                                                                                                                                                                                     
      showChild: false,                                                                                                                                                                               
    ;                                                                                                                                                                                                
                                                                                                                                                                                                     

  render()                                                                                                                                                                                           
    const  showChild  = this.state;                                                                                                                                                                 
    return (                                                                                                                                                                                          
      <View>                                                                                                                                                                                          
        <AnimatedOpacity visible=this.state.showChild />                                                                                                                                            
        <TouchableOpacity onPress=() => this.setState( showChild: !showChild )>                                                                                                                   
          <Text>showChild ? 'Hide' : 'Show'  greeting</Text>                                                                                                                                        
        </TouchableOpacity>                                                                                                                                                                           
      </View>                                                                                                                                                                                         
    );                                                                                                                                                                                                 
                                                                                                                                                                                                     

                                                                                                                                                                                                     

// This is your animated Component                                                                                                                                                                                                   
class AnimatedOpacity extends React.Component                                                                                                                                                        

  constructor(props, ctx)                                                                                                                                                                            
    super(props, ctx);                                                                                                                                                                                
    this.state =                                                                                                                                                                                     
      opacityValue: new Animated.Value(props.visible ? 1 : 0),                                                                                                                                                                                                                                                                                                               
    ;                                                                                                                                                                                                
  

  componentWillReceiveProps(nextProps)                                                                                                                                                               
    if (nextProps.visible !== this.props.visible)                                                                                                                                                    
      this._animate(nextProps.visible);                                                                                                                                                               
                                                                                                                                                                                                     
                                                                                                                                                                                                     

  _animate(visible)                                                                                                                                                                                  
    Animated.timing(this.state.opacityValue,                                                                                                                                                         
      toValue: visible ? 1 : 0,                                                                                                                                                                       
      duration: 350,                                                                                                                                                                                  
    ).start();                                                                                                                                                       
                                                                                                                                                                                                     

  render()                       
    return (                                                                                                                                                                                          
      <Animated.View style= opacity: this.state.opacityValue >                                                                                                                                    
        <Text>Hello World</Text>                                                                                                                                                                      
      </Animated.View>                                                                                                                                                                                
    );                                                                                                                                                                                                 

                                                                                                                                                                                                     

                                                                                                                                                                                                     


export  AnimatedOpacityController, AnimatedOpacity ;

现在开始测试

import React from 'react';                                                                                                                                                                            
import renderer from 'react-test-renderer';                                                                                                                                                           
import  shallow  from 'enzyme';                                                                                                                                                                                                                                                                                                                                                                       

import  AnimatedOpacityController, AnimatedOpacity  from '../AnimatedOpacity';                                                                                                                    


jest.mock('Animated', () =>                                                                                                                                                                          
  const ActualAnimated = require.requireActual('Animated');                                                                                                                                           
  return                                                                                                                                                                                             
    ...ActualAnimated,                                                                                                                                                                                
    timing: (value, config) =>                                                                                                                                                                       
      return                                                                                                                                                                                         
        start: (callback) => 
          value.setValue(config.toValue);
          callback && callback()
        ,                                                                                                                                                  
      ;                                                                                                                                                                                              
    ,                                                                                                                                                                                                
  ;                                                                                                                                                                                                  
);                                                                                                                                                                                                                                                                                                                                                                                                     

it('renders visible', () =>                                                                                                                                                                          
  expect(                                                                                                                                                                                             
    renderer.create(                                                                                                                                                                                  
      <AnimatedOpacity visible=true />                                                                                                                                                              
    ).toJSON()                                                                                                                                                                                        
  ).toMatchSnapshot();                                                                                                                                                                                
);                                                                                                                                                                                                   

it('renders invisible', () =>                                                                                                                                                                        
  expect(                                                                                                                                                                                             
    renderer.create(                                                                                                                                                                                  
      <AnimatedOpacity visible=false />                                                                                                                                                             
    ).toJSON()                                                                                                                                                                                        
  ).toMatchSnapshot();                                                                                                                                                                                
);                                                                                                                                                                                                   

it('makes transition', () =>                                                                                                                                                                         
  const component = shallow(<AnimatedOpacityController />);                                                                                                                                           
  expect(renderer.create(component.node).toJSON()).toMatchSnapshot();                                                                                                                                 
  component.find('TouchableOpacity').simulate('press');                                                                                                                                               
  expect(renderer.create(component.node).toJSON()).toMatchSnapshot();                                                                                                                                 
  component.find('TouchableOpacity').simulate('press');                                                                                                                                               
  expect(renderer.create(component.node).toJSON()).toMatchSnapshot();                                                                                                                                 
);                                                                                                                                                                                                   

现在生成的快照将具有预期的不透明度值。 如果你经常使用动画,你可以将你的 mock 移动到 js/config/jest 并编辑你的 package.json 以在你的所有测试中使用它,那么对你的存根所做的任何更改都将适用于所有测试。

已编辑:

上面的解决方案只解决了从头到尾的问题。更细化的解决方案是:

    不要嘲笑动画 在开玩笑的配置中制作global.requestAnimationFrame = null 使用 mockdate 模拟日期 使用 jest.runTimersToTime 进行时间旅行

时间旅行函数是

const timeTravel = (ms, step = 100) =>                                                                                                                                                                               

  const tickTravel = v =>                                                                                                                                                                                
    jest.runTimersToTime(v);                                                                                                                                                                              
    const now = Date.now();                                                                                                                                                                               
    MockDate.set(new Date(now + v));                                                                                                                                                                      
                                                                                                                                                                                                         

  let done = 0;                                                                                                                                                                                           
  while (ms - done > step)                                                                                                                                                                                
    tickTravel(step);                                                                                                                                                                                      
    done += step;                                                                                                                                                                                          
                                                                                                                                                                                                         
  tickTravel(ms - done);                                                                                                                                                                                  
;    

由于动画内部行为,将步骤分成小块很重要。

【讨论】:

遇到了同样的问题,无法让它工作,你能分享一个示例项目吗?你能解释一下为什么有必要设置 global.requestAnimationFrame = null 并模拟日期吗?谢谢 它不起作用,因为从 react-native 导入时无法模拟导入:`Cannot find module 'Animated from 'BicolorFavoriteCount.test.tsx'`【参考方案2】:

Aspirina 的 EDIT 有助于解决这个问题,但它并没有直接完成工作。对于后面的人,这就是我解决模拟动画进度问题的方法:

我正在使用 Jest - 这是我的 setupTests.js 脚本,用于引导测试环境

const MockDate = require('mockdate')
const frameTime = 10

global.requestAnimationFrame = (cb) => 
    // Default implementation of requestAnimationFrame calls setTimeout(cb, 0),
    // which will result in a cascade of timers - this generally pisses off test runners
    // like Jest who watch the number of timers created and assume an infinite recursion situation
    // if the number gets too large.
    //
    // Setting the timeout simulates a frame every 1/100th of a second
    setTimeout(cb, frameTime)


global.timeTravel = (time = frameTime) => 
    const tickTravel = () => 
        // The React Animations module looks at the elapsed time for each frame to calculate its
        // new position
        const now = Date.now()
        MockDate.set(new Date(now + frameTime))

        // Run the timers forward
        jest.advanceTimersByTime(frameTime)
    

    // Step through each of the frames
    const frames = time / frameTime
    let framesEllapsed
    for (framesEllapsed = 0; framesEllapsed < frames; framesEllapsed++) 
        tickTravel()
    

这里的想法是,我们将 requestAnimationFrame 速率减慢到恰好 100 fps,并且 timeTravel 函数允许您以一帧的时间增量前进。这是一个如何使用它的示例(假设我有一个动画需要一秒钟才能完成):

beforeEach(() => 
    // As part of constructing the Animation, it will grab the
    // current time. Mocking the date right away ensures everyone
    // is starting from the same time
    MockDate.set(0)

    // Need to fake the timers for timeTravel to work
    jest.useFakeTimers()
)

describe('half way through animation', () => 
  it('has a bottom of -175', () => 
    global.timeTravel(500)
    expect(style.bottom._value).toEqual(-175)
  )
)

describe('at end of animation', () => 
  it('has a bottom of 0', () => 
    global.timeTravel(1000)
    expect(style.bottom._value).toEqual(0)
  )
)

【讨论】:

工作愉快。谢谢 当单个js文件中有多个动画时,这将不起作用【参考方案3】:

就我而言,我根本没有使用Animated.View。但相反,我有一个使用requestAnimationFrame 的组件。回调实际上使用了 time 参数,所以我必须在替换 requestAnimationFrame 时将当前时间传递给回调函数,如下所示:

global.requestAnimationFrame = (cb) => 
    setTimeout(() => cb(Date.now()), frameTime)

【讨论】:

【参考方案4】:

您可以模拟Animated.View,使其在测试时表现得像一个常规视图。

jest.mock('react-native', () => 
  const rn = jest.requireActual('react-native')
  const spy = jest.spyOn(rn.Animated, 'View', 'get')
  spy.mockImplementation(() => jest.fn((children) => children));
  return rn
);

我改编自 React Transition Group 的 example of mocking Transition Groups

【讨论】:

【参考方案5】:

您可以通过以下方式模拟 Animated.createAnimatedComponent

jest.mock('react-native', () => 
  const rn = jest.requireActual('react-native');
  const spy = jest.spyOn(rn.Animated, 'createAnimatedComponent');
  spy.mockImplementation(() => jest.fn(() => null));
  return rn;
);

【讨论】:

以上是关于React-Native 应用程序的 Jest 测试 Animated.View的主要内容,如果未能解决你的问题,请参考以下文章

Jest 遇到了带有 react-native 的意外令牌

react-native, jest, ts-jest: ReferenceError: React is not defined

使用 Jest 在 React-Native 中测试组件行为(点击)

如何在 React-Native 项目中使用 Jest 测试样式化组件?

使用 Jest 运行 testing-library/react-native 时 RNEncryptedStorage 未定义

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