为 Discourse 中的 Ember 代码编写验收测试和组件测试

自动化测试是保护您的代码免受未来回归问题影响的好方法。许多人熟悉如何在我们的 Rails 代码库中使用 rspec 进行此类测试,但 JavaScript 方面对一些人来说可能有些神秘。

幸运的是,如今为 Ember 代码添加基本测试非常容易!

组件测试

在本系列的 上一教程 中,我们添加了一个名为 fancy-snack 的组件,用于显示带有渐变背景的零食。现在让我们为它编写一个测试。创建以下文件:

test/javascripts/components/snack-test.js

import componentTest from "helpers/component-test";

moduleForComponent("fancy-snack", { integration: true });

componentTest("test the rendering", {
  template: "{{fancy-snack snack=testSnack}}",

  setup() {
    this.set("testSnack", {
      name: "Potato Chips",
      description: "Now with extra trans fat!",
    });
  },

  test(assert) {
    assert.equal(this.$(".fancy-snack-title h1").text(), "Potato Chips");
    assert.equal(
      this.$(".fancy-snack-description p").text(),
      "Now with extra trans fat!"
    );
  },
});

要运行测试,请在开发服务器上打开浏览器并访问 /qunit?module=component%3Afancy-snack。浏览器将执行组件测试,并输出类似“2 个断言中的 2 个通过,0 个失败”的结果。

请注意,在 /qunit 页面上,您可以运行其他测试。只需从屏幕顶部的 Module 下拉框中选择新的测试即可。

让我们逐步分析该测试,以了解其工作原理。

template 行告诉 Ember 我们希望如何插入组件。它与在 Handlebars 模板中放置组件时使用的标记完全相同,因此您应该很熟悉:

template: '{{fancy-snack snack=testSnack}}',

请注意,它通过 snack 参数传递了 testSnack。该参数在 setup() 方法中定义:

setup() {
  this.set('testSnack', {
    name: 'Potato Chips',
    description: 'Now with extra trans fat!'
  });
},

我只放入了一些模拟数据。这就是让 Ember 渲染组件所需的全部内容。最后,我们在 test() 方法中有一些断言:

test(assert) {
  assert.equal(this.$('.fancy-snack-title h1').text(), 'Potato Chips');
  assert.equal(this.$('.fancy-snack-description p').text(), 'Now with extra trans fat!');
}

如果您使用 this.$(),您就可以在模板中访问 jQuery 选择器。这里的断言使用该选择器获取零食标题和描述的值,并与预期值进行比较。如果值匹配,则断言将通过,测试也就成功了。

值得注意的是,您不需要像这样测试组件中的每一个小细节。您应该有所取舍,尝试找出代码中哪些部分可能会出错或导致其他开发者在未来感到困惑。如果您在模板中测试了太多内容,未来其他人修改它时会非常麻烦。只需从小处着手,测试最明显的内容,久而久之您就会掌握要领。

验收测试

验收测试 通常更容易编写,并且可能比组件测试更强大,因为它们以用户在浏览器中的相同方式测试您的应用程序。我通常从验收测试开始,如果我在开发一个复杂的组件,也会为其添加测试。

以下是我们如何编写一个验收测试,访问 /admin/snack 路由并确认零食已成功渲染:

test/javascripts/acceptance/snack-test.js

import { acceptance } from "helpers/qunit-helpers";
acceptance("Snack");

test("Visit Page", function (assert) {
  visit("/admin/snack");
  andThen(() => {
    assert.ok(exists(".fancy-snack-title"), "the snack title is present");
  });
});

此处的 test() 几乎像英语一样易读!第一条命令是访问 /admin/snack 的 URL。之后,有一个 andThen() 方法。该方法用于确保所有后台工作完成后再继续执行测试。由于 JavaScript 和 Ember 代码是异步的,我们需要确保在断言执行之前 Ember 已完成所有必要操作。最后,它测试 .fancy-snack-title 元素是否存在。

然而,如果您通过访问 /qunit?module=Acceptance%3A%20Snack 运行此测试,会发现测试失败,原因是 AJAX 错误。

如果您还记得,我们的代码包括 Rails 端和 JavaScript 端,其中 JavaScript 端执行 AJAX 请求以获取数据。验收测试运行了 JavaScript 端,但它不知道如何从 Rails 获取数据。

为了解决这个问题,我们需要使用优秀的 pretender 库添加一个模拟响应。打开 test/javascripts/helpers/create-pretender.js 文件,找到包含以下内容的行:

this.get("/admin/plugins", () => response({ plugins: [] }));

在其下方添加一行,返回一个模拟的零食对象,供我们的验收测试使用:

this.get("/admin/snack.json", () => {
  return response({ name: "snack name", description: "snack description" });
});

您可以将上述代码理解为:“对于任何发往 /admin/snack.json 的请求,返回以下 response。”

如果您刷新 URL /qunit?module=Acceptance%3A%20Snack,您的验收测试应该能够通过 pretender 获取数据,测试也将通过。

下一步

您可以尝试构建一个小功能,并添加测试以确保其正常工作。您甚至可以尝试使用 TDD,在编写任何前端代码之前先创建测试。根据您正在开发的内容以及个人偏好,您可能会发现这是一种更愉快的开发方式。祝您好运,编码愉快 :slight_smile:


本文档已进行版本控制 - 建议通过 GitHub 提交更改。

15 个赞

My prior experience in writing qunit tests was based on the other howto

i.e. “acceptance” was simply

acceptance("Purple Tentacle", { loggedIn: true });

I have seen in other plugins where the test code contained “fake” JSON to test against. I wasn’t sure if that would be as good as testing against “real” data, so I wanted to avoid doing it that way.

The six tests passed, but I got a couple of rather angry looking “unhandled request” errors.

After finding an example of some “setup” code, I tried it and it solved the errors.

It works, but I’m not really sure why, nor why it’s needed for some but not for the majority of plugins I’ve seen that have qunit tests.

3 个赞

For a long time we weren’t great with testing plugins, so not many people followed our example and added tests.

You are right to add a response using pretender to handle AJAX calls.

4 个赞

请注意,/qunit 路径已过时。现在是 /tests。我花了一段时间才弄明白 :slight_smile:

1 个赞