【问题标题】:Jest : window.location.assign mock function not called .. is it related to a Promise catch()?开玩笑:没有调用 window.location.assign 模拟函数.. 它与 Promise catch() 有关吗?
【发布时间】:2019-01-07 15:08:19
【问题描述】:

我正在测试以下文件

tests/client/blog.spec.js

    import axios from 'axios';

    import API_BASE from './config';   // '/api/v1/blog/'

    const deletePost = id => {
      console.log('ID: ', id);
      axios.delete(`${API_BASE}/blog/${id}`, {
          headers: { 'Content-type': 'application/json' },
          data: null, // data null is necessary to pass the headers
        })
        .then((result) => {
          console.log('AXIOS RESOLVED: ', result);
          window.location.assign('/admin?cache=false');
          console.log('CALLED window.location.assign with /admin?cache=false');
        })
        .catch((e) => {
          console.log('AXIOS REJECTED: ', e);
          window.location.assign('/admin?cache=true');
          console.log('CALLED window.location.assign with /admin?cache=true');
        });
      };

    const setupDeletePostHandler = () => {
      const links = Array.prototype.slice.call(document.querySelectorAll('.delete-post'), 0);
      if (links.length > 0) {
        links.forEach(el => {
          el.addEventListener('click', e => {
            e.preventDefault();
            e.currentTarget.style.pointerEvents = 'none';
            e.currentTarget.querySelector('i').classList.remove('is-hidden');
            const id = el.dataset.postId;
            return deletePost(id);
          });
        });
      }
    };

    const pageReady = page => {
      switch (page) {
        case 'admin-index':
          setupDeletePostHandler();
          break;
        default:
          break;
      }
    };

    export default {
      pageReady,
    };

具有以下规格:

tests/client/blog.spec.js

    import Blog from '../../src/client/js/blog.js';

    import mockAxios from "axios";

    jest.mock('axios');

    describe('client/blog', () => {

      beforeAll(() => {
        jest.spyOn(window.location, 'assign').mockImplementation(() => {});
      });

      afterEach(() => {
        mockAxios.delete.mockClear();
      });

      afterAll(() => {
        window.location.assign.mockRestore();
      });

      it('set the DeletePostHandler', async function () {
        // WHEN
        const post = '<div class="posts"><div class="post">' +
          '<p>Today should be a great day to be alive!</p>' +
          '<div class="is-hidden">' +
          '<a id="link_1" class="delete-post" href="/admin/edit-post/" data-post-id="">delete<i class="is-hidden"></i></a>' +
          '</div></div>';
        document.body.innerHTML = post;
        Blog.pageReady('admin-index');
        // WHEN
        await document.querySelector('#link_1').click();
        // THEN
        expect(mockAxios.delete).toHaveBeenCalledTimes(1);
        expect(window.location.assign).toHaveBeenCalled();
        console.log('CALLS: ', window.location.assign.mock.calls);
        expect(window.location.assign).toHaveBeenCalledWith('/admin?cache=false');
      });

    });

它(失败了,这是控制台:

__mocks__/axios.js

    export default {
      delete: jest.fn((url) => {
        if (url === '/api/v1/blog/1') {
            return Promise.resolve({
              data: {},
              status: 200,
              statusText: 'OK',
              headers: {}
            });
        } else {
          return Promise.reject({
            data: {},
            status: 400,
            statusText: 'Error',
            headers: {}
          });
        }
      })
    };

console.log

    $ yarn test-client
    yarn run v1.9.4
    $ jest tests/client/*.js
     FAIL  tests/client/blog.spec.js
      client/blog
        ✕ set the DeletePostHandler (47ms)

      ● client/blog › set the DeletePostHandler

        expect(jest.fn()).toHaveBeenCalled()

        Expected mock function to have been called, but it was not called.

          33 |     // THEN
          34 |     expect(mockAxios.delete).toHaveBeenCalledTimes(1);
        > 35 |     expect(window.location.assign).toHaveBeenCalled();
             |                                    ^
          36 |     console.log('CALLS: ', window.location.assign.mock.calls);
          37 |     expect(window.location.assign).toHaveBeenCalledWith('/admin?cache=false');
          38 |   });

          at Object.toHaveBeenCalled (tests/client/blog.spec.js:35:36)
          at tryCatch (node_modules/regenerator-runtime/runtime.js:62:40)
          at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:296:22)
          at Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:114:21)
          at step (tests/client/blog.spec.js:22:191)
          at tests/client/blog.spec.js:22:361

      console.log src/client/js/blog.js:7
        ID:

      console.log src/client/js/blog.js:18
        AXIOS REJECTED:  { data: {}, status: 400, statusText: 'Error', headers: {} }

      console.log src/client/js/blog.js:20
        CALLED window.location.assign with /admin?cache=true

奇怪......日志显示,当 Axios 请求被拒绝时,应该调用模拟函数......有点不是

注意:当我在解析 Axios 请求的情况下测试脚本时,会正确调用 window.location.assign 模拟...

【问题讨论】:

    标签: javascript unit-testing promise jestjs


    【解决方案1】:

    我过去也遇到过类似的问题。

    问题

    catchexpect(window.location.assign).toHaveBeenCalled() 运行并失败时尚未执行。

    详情

    click() 实际上并没有返回任何东西,所以await 没有什么可等待的。

    根据我的经验,调用await 允许PromiseJobs 队列的一个周期,这就是then 运行并且如果Axios 请求得到解决则测试通过的原因。

    catch 似乎需要 PromiseJobs 队列的两个周期,所以它没有被测试继续执行 await 并且断言失败。

    解决方案

    解决方案是在断言之前确保catch 已经运行。

    理想情况下,这是通过在测试中返回 Promiseawait-ing 来完成的,就像您尝试做的那样。这个测试的棘手部分是 click 实际上并没有返回任何东西,所以没有 Promise 等待。

    对于这种不可能await 实际Promise 的情况,一个好的解决方法是await 解析Promise 以获得PromiseJobs 队列所需的周期数。

    每次Promiseawait-ed 测试的其余部分基本上都会排在PromiseJobs 的后面,并允许队列中的任何内容在继续测试之前运行。

    在这种情况下,等待PromiseJobs 队列的两个周期将使catch 有机会运行:

    document.querySelector('#link_1').click();  // remove "await" since nothing is returned
    await Promise.resolve().then();  // wait two cycles of the PromiseJobs queue
    // THEN
    expect(mockAxios.delete).toHaveBeenCalledTimes(1);
    expect(window.location.assign).toHaveBeenCalled();  // SUCCESS
    

    【讨论】:

    • 非常感谢布赖恩!我预料到了这样的问题,但无法识别它......你的评论还表明我们可以从单元测试中学到很多东西,最终重写代码......“如果很难测试,那就重写你的代码!” ...无论它不是客户端代码的最终版本...我曾经使用 Vue.js ...无论您的答案非常明确和有用!
    猜你喜欢
    • 2023-03-17
    • 1970-01-01
    • 2020-12-30
    • 2021-02-08
    • 2018-07-30
    • 1970-01-01
    • 2018-08-17
    • 2019-10-27
    • 1970-01-01
    相关资源
    最近更新 更多