【问题标题】:Why do I have to invoke promise.then() myself to test ES6 promises?为什么我必须自己调用 promise.then() 来测试 ES6 Promise?
【发布时间】:2018-09-20 21:33:51
【问题描述】:

这是this one 的后续问题(无需阅读该问题即可回答此问题)。

以下面的 React 组件为例:

class Greeting extends React.Component {
    constructor() {
        fetch("https://api.domain.com/getName")
            .then((response) => {
                return response.text();
            })
            .then((name) => {
                this.setState({
                    name: name
                });
            })
            .catch(() => {
                this.setState({
                    name: "<unknown>"
                });
            });
    }

    render() {
        return <h1>Hello, {this.state.name}</h1>;
    }
}

使用 Jest,我们可以断言 name 等于从 getName 请求返回的某些文本:

test("greeting name is 'John Doe'", () => {
    const fetchPromise = Promise.resolve({
        text: () => Promise.resolve("John Doe")
    });

    global.fetch = () => fetchPromise;

    const app = shallow(<Application />);

    return fetchPromise.then((response) => response.text()).then(() => {
        expect(app.state("name")).toEqual("John Doe");
    });
});

但是下面的感觉不对:

return fetchPromise.then((response) => response.text()).then(() => {
    expect(app.state("name")).toEqual("John Doe");
});

我的意思是,我在某种程度上复制了测试文件中的实现。

在我的测试中直接调用then()catch() 感觉不对。特别是当response.text() 也返回一个承诺并且我有两个链接的then()s 只是为了断言name 等于John Doe

我来自 Angular,可以调用 $rootScope.$digest() 并在之后进行断言。

难道没有类似的方法可以做到这一点吗?或者有其他方法吗?

【问题讨论】:

  • 为什么不把它包装成一个函数呢?我确信已经有一些承诺期望的包装器。
  • @Sulthan 我仍然想了解发生了什么以及为什么需要这样做。
  • 看测试,貌似不对。它测试了副作用。实际上,您的架构似乎有问题,这迫使您编写过于复杂的测试。
  • @Sulthan 你将如何测试它?什么是架构,只是出于演示目的的请求?请详细说明一下?

标签: reactjs unit-testing asynchronous jestjs es6-promise


【解决方案1】:

在与工作中的同事讨论这个主题后,我将回答我自己的问题,这让我更清楚了。也许上面的问题已经回答了我的问题,但我正在用我更容易理解的语言给出答案。

我自己并没有从最初的实现中调用then(),我只是链接另一个then() 以在其他实现之后执行。

另外,一个更好的做法是将我的fetch() 调用以及所有then()s 和catch()s 放在它自己的函数中并返回承诺,如下所示:

requestNameFromApi() {
    return fetch("https://api.domain.com/getName")
        .then((response) => {
            return response.text();
        })
        .then((name) => {
            this.setState({
                name: name
            });
        })
        .catch(() => {
            this.setState({
                name: "<unknown>"
            });
        });
}

还有测试文件:

test("greeting name is 'John Doe'", () => {
    global.fetch = () => Promise.resolve({
        text: () => Promise.resolve("John Doe")
    });

    const app = shallow(<Application />);

    return app.instance().requestNameFromApi.then(() => {
        expect(app.state("name")).toEqual("John Doe");
    });
});

这更有意义。您有一个“请求函数”返回一个承诺,您可以通过调用它直接测试该函数的输出,然后链接另一个 then() 以在最后被调用,以便我们可以安全地断言我们需要什么。

如果您对在测试中返回承诺的替代方案感兴趣,我们可以像这样编写上面的测试:

test("greeting name is 'John Doe'", async () => {
    global.fetch = () => Promise.resolve({
        text: () => Promise.resolve("John Doe")
    });

    await shallow(<Application />).instance().requestNameFromApi();

    expect(app.state("name")).toEqual("John Doe");
});

【讨论】:

  • @Sulthan 为什么这不是一个好习惯?那你会怎么做呢?
  • 对不起,我想写“更好”。在我看来这并不重要:)
  • 我唯一的困惑是你指的是catch而不是requestNameFromApi函数。
  • 我正在删除我的评论,因为我读错了你的答案,抱歉,一定累了。
【解决方案2】:

我的回答可能很糟糕,但我最近第一次测试了这种功能,并使用了 fetch promises。而这同样的方面让我感到困惑。

我认为您并没有在实现中复制代码,而是不得不推迟您的期望,直到您的模拟/测试双重承诺的异步部分完成。如果你把期望放在那之外,期望将在设置你的状态的存根的异步部分完成之前运行。

基本上,您的测试替身仍然是一个承诺,并且测试中的期望子句依赖于已执行的替身需要在 then 子句中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-26
    • 2017-01-21
    相关资源
    最近更新 更多