Expect(...).toHaveBeenCalled() не работает, даже если вызывается метод компонента React

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

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
0
2 057
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Интересно, что этот вопрос касается некоторых наиболее необычных аспектов 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!
  })
})

Создание функции стрелки при каждом рендеринге может вызвать проблемы с производительностью.

Andrei Dotsenko 19.03.2019 09:37

@AndreiDotsenko согласно официальному React документу: Можно ли использовать стрелочные функции в методах рендеринга?«В целом, да, все в порядке». Если у вас есть проблемы с производительностью, обязательно удалите стрелочную функцию, но затраты на производительность, как правило, очень малы.

Brian Adams 19.03.2019 11:55

На самом деле вы хотите проверить, вызывается ли форма 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). Но так я считаю тест более... скажем, надежным.

Другие вопросы по теме