簡體   English   中英

為 React-Native 應用開玩笑測試 Animated.View

[英]Jest test Animated.View for React-Native app

我嘗試使用 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 時視圖變得可見?

謝謝。

我通過為測試創建動畫存根解決了這個問題。

我看到你使用可見作為屬性,所以一個工作示例是:

組件代碼

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

由於動畫內部行為,將步驟分成小塊很重要。

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

就我而言,我根本沒有使用Animated.View 但相反,我有一個使用requestAnimationFrame的組件。 回調實際上使用了time參數,因此在替換requestAnimationFrame時我必須將當前時間傳遞給回調函數,如下所示:

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

您可以模擬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 的模擬 Transition Groups 示例

您可以通過以下方式模擬 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;
});

現在您可以使用 Jest 為 React Native 組件制作時間旅行動畫。 現在可以刪除其他答案中建議的MockDate包,因為 Jest 本身就支持這種開箱即用的功能。 我發現這是因為MockDate不適用於我的 babel 設置。

這是我修改后的設置:

export const withAnimatedTimeTravelEnabled = () => {
  beforeEach(() => {
    jest.useFakeTimers()
    jest.setSystemTime(new Date(0))
  })
  afterEach(() => {
    jest.useRealTimers()
  })
}

const frameTime = 10
export const timeTravel = (time = frameTime) => {
  const tickTravel = () => {
    const now = Date.now()
    jest.setSystemTime(new Date(now + frameTime))
    jest.advanceTimersByTime(frameTime)
  }
  // Step through each of the frames
  const frames = time / frameTime
  for (let i = 0; i < frames; i++) {
    tickTravel()
  }
}

澄清:

  • 在測試腳本的描述塊(或根塊)中調用withAnimatedTimeTravelEnabled來為測試注冊 Jest 計時器。
  • 觸發動畫后在測試腳本中調用timeTravel

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM