Jest test Animated.View для приложения React-Native

Я пытаюсь проверить Animated.View с Jest для React-Native. Когда я устанавливаю свойство visible правда, это должно оживить мой взгляд от 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();
});

Мне было интересно, как я мог заставить Джеста ждать? Или как я мог проверить это, чтобы убедиться, что вид становится видимым, когда я устанавливаю реквизиты в true?

Благодарю.

6 ответов

Я решил эту проблему, создав анимированную заглушку для тестов.

Я вижу, вы используете 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. Используйте 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 был полезен в решении этой проблемы, но он не сделал работу напрямую. Для тех, кто следует, вот как я решил проблему симуляции прогресса анимации:

Я использую 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 кадров в секунду, а функция 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.примера издевательства над группами перехода

Теперь вы можете использовать Jest для анимации путешествий во времени для компонентов React Native. В настоящее время можно удалить пакет, предложенный в других ответах, поскольку 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.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;
});

В моем случае я не использую Animated.Viewвообще. Но вместо этого у меня есть компонент, который используетrequestAnimationFrame. Обратный вызов фактически используетtime аргумент, поэтому мне пришлось передать текущее время функции обратного вызова при замене requestAnimationFrame вот так:

global.requestAnimationFrame = (cb) => {
    setTimeout(() => cb(Date.now()), frameTime)
}
Другие вопросы по тегам