Я пытаюсь проверить, вызывается ли метод компонента React с использованием фермента и шутки. Предполагается, что функция вызывается, когда элемент <section> теряет фокус (при размытии). Компонент подключен к хранилищу Redux, но он также экспортируется по имени, чтобы исключить Redux из уравнения. Ниже представлена упрощенная версия компонента:
export class Section extends React.Component {
constructor(props) {
super(props)
this.formRef = React.createRef();
this.submitForm = this.submitForm.bind(this);
this.state = {
formSubmitted: false
}
}
submitForm() {
console.info("form submitted");
this.setState({ formSubmitted: true })
if (this.formRef && this.formRef.current) {
this.formRef.current.submit();
}
}
render() {
return (
<section onBlur = {this.submitForm}>
<form ref = {this.formRef} action = {url} method='post'>
<input type = "text" name = "something" />
</form>
</section>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Section);
Я пробовал всевозможные комбинации для части spyOn, так как это, кажется, корень проблемы. Все потерпели неудачу.
import React from 'react';
import { shallow } from 'enzyme';
import { Section } from './Section';
describe('Section', () => {
it('should submit form on blur', () => {
const wrapper = shallow(<Section content = {{ body: [] }} />);
const spy = spyOn(wrapper.instance(), 'submitForm');
const spy2 = spyOn(Section.prototype, 'submitForm');
const spy3 = jest.spyOn(wrapper.instance(), 'submitForm');
const spy4 = jest.spyOn(Section.prototype, 'submitForm');
wrapper.find('section').simulate('blur');
expect(wrapper.state().formSubmitted).toEqual(true);
expect(spy).toHaveBeenCalled();
expect(spy2).toHaveBeenCalled();
expect(spy3).toHaveBeenCalled();
expect(spy4).toHaveBeenCalled();
})
})
Я дал компоненту состояние и протестировал его в дополнение к тестированию expect(...).toHaveBeenCalled, чтобы проверить, действительно ли была вызвана функция, и, похоже, это так.
В консоли появится console.info('form submitted').
Тест expect(wrapper.state().formSubmitted).toEqual(true); проходит, что указывает мне на то, что вызывается правильная функция. Однако я не хочу иметь ненужное состояние только для теста. Только что было добавлено состояние, подтверждающее, что вызывается метод «submitForm».
Все утверждения expect(...).toHaveBeenCalled() терпят неудачу с ошибкой Expected spy to have been called, but it was not called или Expected mock function to have been called, but it was not called.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Интересно, что этот вопрос касается некоторых наиболее необычных аспектов JavaScript.
submitForm изначально определяется как метод-прототип:
class Section extends React.Component {
...
submitForm() { ... }
...
}
... это означает, что вы должны создать spy следующим образом:
jest.spyOn(Section.prototype, 'submitForm');
... но затем он переопределяется как свойство экземпляра в constructor:
this.submitForm = this.submitForm.bind(this);
... это означает, что вы должны сейчас создать spy следующим образом:
jest.spyOn(wrapper.instance(), 'submitForm');
... но этот еще не работает, потому что onBlur привязывается напрямую к this.submitForm во время render:
<section onBlur = {this.submitForm}>
Таким образом, способ написания кода в настоящее время фактически делает невозможным слежку за submitForm, потому что для создания spy требуется instance, но instance недоступен до тех пор, пока компонент не отрендерится, а onBlur не будет привязан непосредственно к this.submitForm во время рендеринга.
Измените onBlur на функция, которая вызывает this.submitForm, чтобы всякий раз, когда onBlur срабатывает, он вызывал текущая стоимость из this.submitForm:
render() {
return (
<section onBlur = {() => this.submitForm()}>
<form ref = {this.formRef} action = {url} method='post'>
<input type = "text" name = "something" />
</form>
</section>
);
}
...тогда, когда вы замените submitForm на spy в instance, spy будет вызываться при срабатывании onBlur:
describe('Section', () => {
it('should submit form on blur', () => {
const wrapper = shallow(<Section content = {{ body: [] }} />);
const spy = jest.spyOn(wrapper.instance(), 'submitForm');
wrapper.find('section').simulate('blur');
expect(wrapper.state().formSubmitted).toEqual(true);
expect(spy).toHaveBeenCalled(); // Success!
})
})
@AndreiDotsenko согласно официальному React документу: Можно ли использовать стрелочные функции в методах рендеринга?«В целом, да, все в порядке». Если у вас есть проблемы с производительностью, обязательно удалите стрелочную функцию, но затраты на производительность, как правило, очень малы.
На самом деле вы хотите проверить, вызывается ли форма submit(), когда Section теряет фокус, верно? Таким образом, вам не нужно проверять, был ли вызван внутренний метод - он не будет гарантировать, отправлена форма или нет. Также это приведет к ложноотрицательным результатам в случае рефакторинга (внутренний метод переименован и все работает, но тест не проходит).
Вместо этого вы можете издеваться над объектом ref. Затем вы сможете проверить, была ли отправлена форма
it('should submit form on blur', () => {
const wrapper = shallow(<Section content = {{ body: [] }} />);
wrapper.instance().formRef.current = { submit: jest.fn()};
wrapper.find('section').simulate('blur');
expect(wrapper.instance().formRef.current.submit).toHaveBeenCalled();
})
Уверенный тест также полагается на внутренние компоненты компонента (на свойство, которое содержит ref). Но так я считаю тест более... скажем, надежным.
Создание функции стрелки при каждом рендеринге может вызвать проблемы с производительностью.