Фермент: при имитации «щелчка» проверяется асинхронно измененное значение состояния - PullRequest
1 голос
/ 08 марта 2019

Я пытаюсь проверить значение state (скажем, state.name), которое асинхронно изменяется при нажатии кнопки.

Первоначально

  • state.name является "Random Value"
  • При нажатии кнопки state.name изменяется на "peter" с "Random Value"
  • работает как положено в браузере
  • Нужно, чтобы он работал на button.simulate('click') в моем тесте

Мой компонент App.js:

import React, { Component } from 'react';

class App extends Component {
    state = {name: "Random Value"};
    render() {
        return (
            <div className="App">
                <div>{this.state.name}</div>
                <button onClick={this.handleClick}>GetData</button>
            </div>
        );
    }

    handleClick = () => {
        const currentContext = this;
        fetch('http://api-call/getdata')
            .then(function(response) {
                return response.json();
            })
            .then(function(jsonData) {
                // jsonData.name = "peter"
                currentContext.setState({name: jsonData.name});
            })
    }
}

export default App;

Тестовый файл App.test.js

describe('<App/>', () => {    
    it('should handle click correctly', () => {
        const wrapper = shallow(<App />);

        expect(wrapper.find('button').length).toBe(1);

        expect(wrapper.state().name).toEqual("Random Value");
        wrapper.find('button').simulate('click');
        expect(wrapper.update().state().name).toEqual("peter");
    });
});

Результат:

// FAILED!

Expected value to equal:
      "peter"
Received:
      "Random Value"

Что еще я пробовал?

Разнообразные решения, такие как async await, setImmediate, затем wrapper.update() и многие другие. Может быть, я сделал что-то не так или что-то упустил. В любом случае я провел вечер, пытаясь это сделать. Мне нужна помощь от enzyme экспертов.

Спасибо

1 Ответ

3 голосов
/ 09 марта 2019

Сначала тебе нужно как-то издеваться fetch. Отправка реального запроса не только нарушает изоляцию юнит-тестов, но и повышает риск возникновения противоречий. Также сложнее ждать ответа, когда вы не знаете, когда он может закончиться. Есть разные способы достичь этого. jest-fetch-mock является одним из них.

Также я советую вам не проверять state, а проверять render() результаты.

function getName(wrapper) {
    return wrapper.find('.App > div').at(0).props().children;
} 

it('should handle click correctly', async () => {
    fetch.mockResponseOnce(JSON.stringify({ name: '12345' }));
    const wrapper = shallow(<App />);

    expect(wrapper.find('button').length).toBe(1);

    expect(getName(wrapper)).toEqual("Random Value");
    wrapper.find('button').simulate('click');
    await Promise.resolve();
    expect(getName(wrapper)).toEqual("peter");
});

Что здесь происходит. await Promise.resolve() просто ждет, пока все выполненные обещания не будут выполнены. Это означает, что без этого наш поддельный ответ не будет выполняться между нажатием кнопки и expect.

Еще один способ получить положительный результат - заставить handleClick() вернуть Обещание, которого мы можем ожидать:

...
handleClick = () => {
    const currentContext = this;
    return fetch('http://api-call/getdata')
        .then(function(response) {
            return response.json();
        })
        .then(function(jsonData) {
            // jsonData.name = "peter"
            currentContext.setState({name: jsonData.name});
        })
}
....
it('should handle click correctly', async () => {
     ....
    expect(getName(wrapper)).toEqual("Random Value");
    await wrapper.find('button').simulate('click');
    expect(getName(wrapper)).toEqual("peter");
});

или без синтаксического асинхронного ожидания:

it('should handle click correctly', () => {
     ....
    expect(getName(wrapper)).toEqual("Random Value");
    return wrapper.find('button').simulate('click').then(() => {
        expect(getName(wrapper)).toEqual("peter");
    });
});

Но мне действительно не нравится этот способ, так как обработчик событий должен возвращать Promise, который он обычно не делает.

Подробнее о микрозадачах (обещаниях) / макрозадачах (таймерах, событиях), которые вы можете прочитать здесь: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f

Подробнее о тестировании асинхронного кода в jest, лучше посмотрите их документы: https://jestjs.io/docs/en/asynchronous

...