ШУТА | Утверждение, что функция была вызвана внутри обратного вызова addEventListener

У меня есть следующая функция:

Код для тестирования

export default function main() {
    const createAndAppendPTag = () => {
        const p = document.createElement('p');
        document.body.appendChild(p);
    };

    window.document.addEventListener('click', () => {
        createAndAppendPTag();
    });
}

Вопрос в том, как я могу утверждать с помощью Jest, что createAndAppendPTag вызывается при событии щелчка по документу?



Шутка

Это то, что я пробовал, но не могу пройти тест:

import main from './main'

window.document.addEventListener = jest.fn();
const createAndAppendPTag = jest.fn();

describe('Main', () => {
    const documentClickEvent = new Event('click');

    test('appends p tag to the document', () => {
        // dispatching event before and after invoking `main` to be sure
        window.document.dispatchEvent(documentClickEvent);

        main();

        window.document.dispatchEvent(documentClickEvent);

        expect(window.document.addEventListener).toHaveBeenNthCalledWith(1, 'click', () => {});
        expect(createAndAppendPTag).toHaveBeenCalledTimes(1);
    });
});

Терминал

Это приводит к следующему:

🔴  Main › appends p tag to the document
    
expect(jest.fn()).toHaveBeenNthCalledWith(n, ...expected)
    
n: 1
Expected: "click", [Function anonymous]
    
Number of calls: 0
    
5   | main();
6   | window.document.dispatchEvent(documentClickEvent);
> 7 | expect(window.document.addEventListener).toHaveBeenNthCalledWith(1, 'click', () => {});
*   |                                          ^

Заранее спасибо.

createAndAppendPTag является приватным для основного, поэтому вы не видите его во время тестирования. Можете ли вы просто проверить побочный эффект напрямую (p был добавлен в тело)?
terrymorse 25.12.2020 20:50

Спасибо за предложение @terrymorse, я пытался сделать это в течение последнего часа или около того, но безуспешно, кажется, мне нужно позже задать еще один вопрос о запросе домена в тесте, потому что я получаю пустое тело, до тех пор возможно, кто-то может предоставить другое предложение/решение.

Sam Lahm 25.12.2020 23:44
Поведение ключевого слова "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) для оценки ваших знаний,...
1
2
6 472
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать jest.spyOn(object, methodName) для создания макетов для методов window.document.addEventListener(), document.createElement() и document.body.appendChild().

Поскольку функция createAndAppendPTag является приватной, вы не можете шпионить/издеваться над ней, но вы можете косвенно определить, вызывается ли она, утверждая ее внутренний метод.

Например. используя "jest": "^26.6.3":

main.js:

export default function main() {
  const createAndAppendPTag = () => {
    const p = document.createElement('p');
    document.body.appendChild(p);
  };

  window.document.addEventListener('click', () => {
    createAndAppendPTag();
  });
}

main.test.js:

import main from './main';

describe('65451115', () => {
  afterAll(() => {
    jest.restoreAllMocks();
  });
  it('should pass', () => {
    const createElementSpy = jest.spyOn(document, 'createElement').mockReturnValue('fake p');
    const appendChildSpy = jest.spyOn(document.body, 'appendChild').mockReturnValue();
    const addEventListenerSpy = jest
      .spyOn(window.document, 'addEventListener')
      .mockImplementationOnce((event, handler) => {
        handler();
      });
    main();
    expect(addEventListenerSpy).toBeCalledWith('click', expect.any(Function));
    expect(createElementSpy).toBeCalledWith('p');
    expect(appendChildSpy).toBeCalledWith('fake p');
  });
});

результат модульного теста:

 PASS  examples/65451115/main.test.js (10.621 s)
  65451115
    ✓ should pass (4 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 main.js  |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        16.536 s

Спасибо за идею! Есть ли шанс, что я могу утверждать, что createAndAppendPTag был вызван? (даже если мне придется изменить исходный код)

Sam Lahm 26.12.2020 08:40

@SamLahm Вам нужно разоблачить его, чтобы вы могли установить шпиона или издеваться над ним. Тогда вы можете утверждать, что это было вызвано или нет.

Lin Du 26.12.2020 08:48
Снова некоторые комментарии о том, является ли это хорошей практикой, могут быть полезны. Вы издеваетесь над интерфейсом, которым не владеете, и тестируете реализацию, а не поведение. Выставлять детали реализации только для того, чтобы поиздеваться над ними для теста, еще хуже.
jonrsharpe 26.12.2020 11:02
Ответ принят как подходящий

Я провел этот упрощенный тест, чтобы проверить наличие побочного эффекта (элемент p был добавлен к телу):

main.js

export default function main() {
  const createAndAppendPTag = () => {
    const p = document.createElement('p');
    document.body.appendChild(p);
  };

  window.document.addEventListener('click', () => {
    createAndAppendPTag();
  });
}

main.test.js

import main from `../main.js`;

it('"main" listener appends "P" to body upon click', () => {
  // add listener
  main();

  // clear body contents
  document.body.innerHTML = "";

  // dispatch click event to listener
  const addEvt = new Event('click');
  document.dispatchEvent(addEvt);

  // check for existence of "P" element
  const bodyEl = document.body.firstChild;
  expect(bodyEl).not.toEqual(null);
  expect(bodyEl.tagName).toBe('P');
  document.body.innerHTML = "";
});

Прошло:

  ✓ "main" listener appends "P" to body upon click (2 ms)

Спасибо, Терри, как вы предложили, я должен утверждать побочный эффект в таких случаях. Отметить это как правильный ответ (просто typeof bodyEl не требуется, так как все в JS является объектом: D)

Sam Lahm 27.12.2020 00:53

@SamLahm Ну, не все в JS является объектом. Есть string, number, boolean и undefined, и это лишь некоторые из них. Хотя null — это объект, что является проблемой. Так что этот тест лучше: expect(bodyEl).not.toEqual(null) (ответ изменю).

terrymorse 30.03.2021 15:50

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