Jest test Animated.View for React-Native app



我尝试用 Jest 为 React-Native 测试一个Animated.View。当我将属性设置为 truevisible时,它应该将我的视图从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();
});

我想知道我怎么能让开玩笑等待?或者我如何测试这一点以确保当我将道具设置为 true 时视图变得可见?

谢谢。

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

我看到您正在使用 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();                                                                                                                                 
});                                                                                                                                                                                                   

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

编辑:

上面的解决方案只能从头到尾解决。更精细的解决方案是:

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

这里的想法是,我们将请求动画帧速率减慢到正好 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,使其在测试时表现得像常规视图一样。

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 Group 的例子

现在你可以使用 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

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

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

你可以通过以下方式模拟 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;
});

相关内容

  • 没有找到相关文章

最新更新