开玩笑:window.location.assign模拟函数未调用..它与Promise catch()有关吗?

时间:2019-01-07 15:08:20

标签: javascript unit-testing promise jestjs

我正在测试以下文件

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模拟...

1 个答案:

答案 0 :(得分:1)

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

问题

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

详细信息

click()实际上不返回任何内容,因此await没有什么可等待的。

根据我的经验,调用await可以使PromiseJobs队列有一个循环,这就是then运行并在Axios请求解决后测试通过的原因。

catch似乎要花费两个周期的PromiseJobs队列,因此它没有被测试执行,直到await为止,并在断言时失败。

解决方案

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

理想情况下,这是通过返回Promiseawait并将其放入测试中来完成的,就像您尝试这样做一样。此测试的棘手部分是click实际上没有返回任何内容,因此没有Promise可以等待。

在这种情况下,无法await实际的Promise是一个好的解决方法,就是await已解决Promise所需的周期数PromiseJobs队列。

每次Promiseawait处理时,其余的测试实际上都在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