我尝试使用Jest for React-Native测试Animated.View
。当我将属性visible
设置为true时,它应该将我的视图从opacity 0
设置为opacity 1
。
这是我的组件呈现的内容:
<Animated.View
style={{
opacity: opacityValue,
}}
>
<Text>{message}</Text>
</Animated.View>
当道具opacityValue
发生变化时,visible
会更新:
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时视图变得可见?
感谢。
答案 0 :(得分:16)
我通过为测试创建动画存根来解决这个问题。
我看到你使用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以在所有测试中使用它,然后对存根进行的任何更改都将可用于所有测试。
编辑:
上述解决方案仅解决了从头到尾的问题。更精细的解决方案是:
global.requestAnimationFrame = null
时间旅行功能
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);
};
由于动画内部行为,在小块中突破步骤是重要的。
答案 1 :(得分:3)
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)
})
})
答案 2 :(得分:0)
就我而言,我根本没有使用Animated.View
。但是,我有一个使用requestAnimationFrame
的组件。回调实际上利用了time
参数,因此在替换requestAnimationFrame
时,我必须将当前时间传递给回调函数,如下所示:
global.requestAnimationFrame = (cb) => {
setTimeout(() => cb(Date.now()), frameTime)
}