【问题标题】:JEST | Assert a function was called inside addEventListener callback开玩笑 |断言在 addEventListener 回调中调用了一个函数
【发布时间】:2021-04-03 15:51:34
【问题描述】:

我有以下功能:

要测试的代码

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 是 main 私有的,因此您在测试时看不到它。你能简单地直接测试副作用吗(p 被添加到正文中)?
  • 感谢@terrymorse 的建议,过去一个小时左右我一直在尝试这样做,但没有运气,看来我需要稍后再问一个关于在测试中查询 dom 的问题,因为我身体越来越空虚,在那之前也许有人可以提供另一个建议/解决方案。

标签: javascript unit-testing jestjs


【解决方案1】:

我运行了这个简化的测试来检查副作用(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)

【讨论】:

  • 谢谢特里,正如你所建议的,我应该断言在这种情况下会产生副作用。将此标记为正确答案(imo 不需要 typeof bodyEl,因为 JS 中的所有内容都是对象:D)
  • @SamLahm 嗯,不是 JS 中的 everything 都是对象。有stringnumberbooleanundefined 等等。虽然null 是一个对象,但这是一个问题。所以这个测试更好:expect(bodyEl).not.toEqual(null)(我会改变答案)。
【解决方案2】:

您可以使用jest.spyOn(object, methodName)window.document.addEventListener()document.createElement()document.body.appendChild() 方法创建模拟。

由于createAndAppendPTag函数是私有的,你不能spy/mock它,但是你可以通过断言它的内部方法间接判断它是否被调用。

例如使用"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 被调用了吗? (即使我必须更改源代码)
  • @SamLahm 你需要暴露它,这样你就可以安装spy或者mock它。然后你可以断言它是否被调用。
  • Again 一些关于这是否是好的做法的评论可能会有所帮助。您正在模拟一个您不拥有的接口,并测试实现而不是行为。公开一个实现细节只是为了模拟它进行测试更糟糕。
猜你喜欢
  • 2020-12-16
  • 2018-08-14
  • 2018-02-14
  • 2018-05-22
  • 2018-12-07
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
  • 1970-01-01
相关资源
最近更新 更多