【问题标题】:Jest test Animated.View for React-Native appReact-Native 应用程序的 Jest 测试 Animated.View
【发布时间】: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 时视图变得可见?

谢谢。

【问题讨论】:

    标签: javascript unit-testing react-native jestjs


    【解决方案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 以在你的所有测试中使用它,那么对你的存根所做的任何更改都将适用于所有测试。

    已编辑:

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

    1. 不要嘲笑动画
    2. 在开玩笑的配置中制作global.requestAnimationFrame = null
    3. 使用 mockdate 模拟日期
    4. 使用 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;
        });
        

        【讨论】:

          猜你喜欢
          • 2016-09-14
          • 2016-12-14
          • 1970-01-01
          • 1970-01-01
          • 2016-07-22
          • 1970-01-01
          • 2018-01-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多