开玩笑-在模拟的异步函数中调用检查本地存储

时间:2018-12-10 22:50:36

标签: javascript reactjs jestjs

我在一个像这样的react组件中有一个api调用。

login = () => {
   // <--- If I set the localStorage on this line the test passes.
   apiRequest.then(res => {
      localStorage.setItem('token', res.token);
   });
}

为了测试它,我模拟了api调用。我想检查是否调用了本地存储,因此也要对localStorage进行模拟,但是,由于在模拟api调用中设置了localStorage,因此永远不会调用它。我的测试代码如下。有谁知道我如何检查模拟呼叫中是否设置了本地存储。我已经确认,如果将localStorage移到apiRequest之外,则可以正确模拟,问题肯定是它在apiRequest中。

// This mocks out the api call
jest.mock('./api', () => {
    return {
        apiRequest: jest.fn(
           () =>
             new Promise(resolve => {
                resolve();
             })
        ),
    };
});


const localStorageMock = (() => {
   const store = {};
   return {
      setItem: jest.fn((key, value) => {
          store[key] = value.toString();
      })
   }
})();

Object.defineProperty(window, 'localStorage', {
   value: localStorageMock
});

it('sets a token in local storage', () => {
    const { getByText } = render(<Login />);
    const loginButton = getByText(/login/i);
    // This passes
    expect(apiRequest).toBeCalledTimes(1);
    // This never gets called as it is being called in the apiRequest
    expect(localStorage.setItem).toBeCalledWith('token', '1234');
});

如果不清楚,请告诉我,我将提供更多详细信息。

2 个答案:

答案 0 :(得分:1)

localStorage.setItem通过.then

异步调用
login = () => {
   apiRequest.then(res => {
      localStorage.setItem('token', res.token);
   });
}

因此,模拟对于异步流没有任何帮助。这小部分

   .then(res => {
      localStorage.setItem('token', res.token);
   }

只是放在队列的末尾(如果您对详细信息感兴趣,则命名为microtask queue

因此,只有在执行此小型微任务之后,您的测试代码才结束。

您如何处理?您可以在async way中编写测试,并将其他expect放入专用微任务中,该微任务将在调用localStorage.setItem的微任务之后运行。

您可以为此使用setTimeout(宏任务):

it('sets a token in local storage', done => {
    const { getByText } = renderLogin();
    const loginButton = getByText(/login/i);
    expect(apiRequest).toBeCalledTimes(1);
    setTimeout(() => {
        // runs after then(....setItem) has been called
        expect(localStorage.setItem).toBeCalledWith('token');
        done();
    }, 0);
});

或通过Promise / async / await创建微任务:

it('sets a token in local storage', async () => {
    const { getByText } = renderLogin();
    const loginButton = getByText(/login/i);
    expect(apiRequest).toBeCalledTimes(1);
    await Promise.resolve(); // everything below goes into separate microtask
    expect(localStorage.setItem).toBeCalledWith('token');
});

关于await的[UPD]有趣的事情是,它不仅可以与Promise一起使用,还可以与其他所有东西一起使用。它可以像Promise.resolve(<some value here>)一样工作。因此,就您而言

it('sets a token in local storage', async () => {
    const { getByText } = renderLogin();
    const loginButton = getByText(/login/i);
    await expect(apiRequest).toBeCalledTimes(1);
    expect(localStorage.setItem).toBeCalledWith('token');
});

也可以。但是我相信它看起来令人困惑(“ waaaat?.toHaveBeenCalled()返回Promise真实吗?!”)和可疑(这是一种魔术!我不能碰它!)。因此,最好选择一些具有直接“延迟”功能的版本

答案 1 :(得分:0)

当您尝试测试异步代码时,一个常见的问题是您还需要进行异步测试,尝试将awaitapiRequest进行解析,然后验证是否调用了本地存储。 / p>