[英]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 以在所有測試中使用它,然后對你的存根所做的任何更改都將可用於所有測試。
編輯:
上面的解決方案只能從頭到尾解決。 更細粒度的解決方案是:
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);
};
由於動畫內部行為,將步驟分成小塊很重要。
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.