Frontend testing standards and style guidelines

原文:https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html

Frontend testing standards and style guidelines

在 GitLab 上开发前端代码时,会遇到两种测试套件. 我们将 Karma 与 Jasmine 和 Jest 一起用于 JavaScript 单元和集成测试,将 RSpec 功能测试与 Capybara 一起用于 e2e(端对端)集成测试.

需要为所有新功能编写单元和功能测试. 大多数时候,您应该使用RSpec进行功能测试.

应该编写回归测试来修复错误,以防止将来再次发生.

See the Testing Standards and Style Guidelines page for more information on general testing practices at GitLab.

Vue.js testing

如果您正在寻找有关 Vue 组件测试的指南,则可以立即跳至本部分 .

Jest

我们已经开始将前端测试迁移到Jest测试框架(另请参见相应的epic ).

开玩笑的测试可以在 EE 的/spec/frontend/ee/spec/frontend中找到.

Note:

大多数示例都有 Jest 和 Karma 示例. 如果您在发现过程中偶然发现了一些用例,请仅参阅 Karma 示例以解释代码中发生的事情. 开玩笑的例子是您应该遵循的例子.

Karma test suite

当 GitLab 切换到Jest 时,您仍然会在我们的应用程序中找到 Karma 测试. Karma是使用Jasmine作为测试框架的测试运行程序. Jest 还使用 Jasmine 作为基础,这就是为什么它看起来非常相似的原因.

业力测试存在于/ee/spec/javascripts中的spec/javascripts//ee/spec/javascripts中.

app/assets/javascripts/behaviors/autosize.js可能具有相应的spec/javascripts/behaviors/autosize_spec.js文件.

请记住,在 CI 环境中,这些测试是在无头浏览器中运行的,您将无权访问某些必须进行存根的 API,例如Notification .

When should I use Jest over Karma?

如果您需要更新现有的 Karma 测试文件(可在spec/javascripts找到),则无需将整个规范迁移到 Jest. 只需更新 Karma 规范以测试您的更改就可以了. 在单独的合并请求中迁移到 Jest 可能更合适.

如果创建新的测试文件,则需要在 Jest 中创建它. 这将有助于支持我们的迁移,我们认为您会喜欢使用 Jest.

与往常一样,请谨慎使用. Jest 解决了我们在 Karma 中遇到的许多问题,并提供了更好的开发人员体验,但是可能会出现潜在的意外问题(尤其是针对浏览器特定功能进行测试).

Differences to Karma

  • Jest 在 Node.js 环境中运行,而不是在浏览器中. 运行在浏览器中测试玩笑支持计划 .
  • 由于 Jest 在 Node.js 环境中运行,因此默认情况下它使用jsdom . 另请参见下面的限制 .
  • Jest 无权访问 Webpack 加载程序或别名. Jest 使用的别名是在其自己的配置中定义的.
  • setTimeoutsetInterval所有调用都被模拟掉了. 另请参阅Jest Timer Mocks .
  • rewire不需要因为玩笑支撑嘲笑模块. 另请参见” 手动模拟” .
  • 没有上下文对象传递给 Jest 中的测试. 这意味着this.somethingbeforeEach()it()之间共享this.something不起作用. 相反,您应该在需要共享变量的上下文中声明它们(通过const / let ).
  • 以下将导致测试在 Jest 中失败:
    • 未模拟的请求.
    • 未处理的承诺拒绝.
    • 调用console.warn ,包括来自 Vue 之类的库的警告.

Limitations of jsdom

如上所述 ,Jest 使用 jsdom 而不是浏览器来运行测试. 这有很多限制,即:

另请参阅有关在浏览器中运行 Jest 测试的支持问题.

Debugging Jest tests

运行yarn jest-debug将在调试模式下运行 Jest,从而允许您按照Jest docs中的说明进行调试/检查.

Timeout error

Jest 的默认超时是在/spec/frontend/test_setup.js设置的.

如果您的测试超过该时间,它将失败.

如果无法提高测试性能,则可以使用setTestTimeout来增加特定测试的超时时间.

  1. import { setTestTimeout } from 'helpers/timeout';
  2. describe('Component', () => {
  3. it('does something amazing', () => {
  4. setTestTimeout(500);
  5. // ...
  6. });
  7. });

请记住,每个测试的性能取决于环境.

What and how to test

在深入了解有关 Jest 特定工作流程(如模拟和间谍)的更具体细节之前,我们应该简要介绍一下使用 Jest 测试的内容.

Don’t test the library

库是任何 JavaScript 开发人员生活中不可或缺的一部分. 一般建议是不要测试库的内部,但是希望库知道它应该做什么并且自己进行测试覆盖. 一个一般的例子可能是这样的

  1. import { convertToFahrenheit } from 'temperatureLibrary'
  2. function getFahrenheit(celsius) {
  3. return convertToFahrenheit(celsius)
  4. }

测试我们的getFahrenheit函数没有任何意义,因为在其下面除了调用库函数外,什么都没有做,而且我们可以预期它正在按预期工作. (简化,我知道)

让我们看一下 Vue 领域. Vue 是 GitLab JavaScript 代码库的关键部分. 在编写 Vue 组件规范时,通常的陷阱是最终测试 Vue 提供的功能,因为这似乎是最容易测试的事情. 这是取自我们代码库的示例.

  1. // Component
  2. {
  3. computed: {
  4. hasMetricTypes() {
  5. return this.metricTypes.length;
  6. },
  7. }

这是相应的规格

  1. describe('computed', () => {
  2. describe('hasMetricTypes', () => {
  3. it('returns true if metricTypes exist', () => {
  4. factory({ metricTypes });
  5. expect(wrapper.vm.hasMetricTypes).toBe(2);
  6. });
  7. it('returns true if no metricTypes exist', () => {
  8. factory();
  9. expect(wrapper.vm.hasMetricTypes).toBe(0);
  10. });
  11. });
  12. });

测试hasMetricTypes计算的道具似乎是给定的,但是要测试所计算的属性是否返回metricTypes的长度, metricTypes测试 Vue 库本身. 除了将其添加到测试套件之外,这没有任何价值. 最好以用户与之交互的方式对其进行测试. 大概通过模板.

请注意这些测试,因为它们只会使更新逻辑变得比所需的更加脆弱和乏味. 其他库也是如此.

前端单元测试部分中可以找到更多示例.

Don’t test your mock

另一个常见的陷阱是规范最终验证了该模拟程序是否正常运行. 如果使用模拟,则模拟应支持测试,但不应成为测试的目标.

Bad:

  1. const spy = jest.spyOn(idGenerator, 'create')
  2. spy.mockImplementation = () = '1234'
  3. expect(idGenerator.create()).toBe('1234')

Good:

  1. const spy = jest.spyOn(idGenerator, 'create')
  2. spy.mockImplementation = () = '1234'
  3. // Actually focusing on the logic of your component and just leverage the controllable mocks output
  4. expect(wrapper.find('div').html()).toBe('<div id="1234">...</div>')

Follow the user

在繁重的组件环境中,单元测试和集成测试之间的界限可能非常模糊. 最重要的准则如下:

  • 编写干净的单元测试,以孤立的方式测试复杂的逻辑部分是否具有实际价值,以防止将来被破坏
  • 否则,请尝试将规格写得尽可能接近用户的需求

例如,与手动调用方法并验证数据结构或计算的属性相比,使用生成的标记来触发按钮单击并验证相应更改的标记更好. 在测试通过并提供错误的安全感的同时,总是有偶然破坏用户流的机会.

Common practices

接下来,您会发现一些通用的常规做法,它们将作为我们测试套件的一部分. 如果您发现不遵循本指南的内容,最好立即进行修复.

How to query DOM elements

当涉及到测试中的 DOM 元素查询时,最好以唯一且语义上为目标的元素. 有时这无法切实可行. 在这些情况下,添加测试属性以简化选择器可能是最佳选择.

优选地,在使用@vue/test-utils进行组件测试时,您应该使用组件本身来查询子组件. 这有助于强制执行该组件的各个单元测试可以涵盖的特定行为. 否则,请尝试使用:

  • 语义属性(例如name (还验证name是否正确设置)
  • 一个data-testid属性( @vue/test-utils维护者推荐
  • Vue ref (如果使用@vue/test-utils

Examples:

  1. it('exists', () => {
  2. // Good
  3. wrapper.find(FooComponent);
  4. wrapper.find('input[name=foo]');
  5. wrapper.find('[data-testid="foo"]');
  6. wrapper.find({ ref: 'foo'});
  7. // Bad
  8. wrapper.find('.js-foo');
  9. wrapper.find('.btn-primary');
  10. wrapper.find('.qa-foo-component');
  11. wrapper.find('[data-qa-selector="foo"]');
  12. });

不建议您仅添加.js-*类用于测试目的. 仅当没有其他可行的选择时才这样做.

除 QA 端到端测试外,请勿将.qa-*类或data-qa-selector属性用于任何测试.

Naming unit tests

在编写描述测试块以测试特定功能/方法时,请使用方法名称作为描述块名称.

  1. // Good
  2. describe('methodName', () => {
  3. it('passes', () => {
  4. expect(true).toEqual(true);
  5. });
  6. });
  7. // Bad
  8. describe('#methodName', () => {
  9. it('passes', () => {
  10. expect(true).toEqual(true);
  11. });
  12. });
  13. // Bad
  14. describe('.methodName', () => {
  15. it('passes', () => {
  16. expect(true).toEqual(true);
  17. });
  18. });

Testing promises

在测试 Promises 时,应始终确保测试是异步的并且拒绝被处理. 现在可以在测试套件中使用async/await语法:

  1. it('tests a promise', async () => {
  2. const users = await fetchUsers()
  3. expect(users.length).toBe(42)
  4. });
  5. it('tests a promise rejection', async () => {
  6. expect.assertions(1);
  7. try {
  8. await user.getUserName(1);
  9. } catch (e) {
  10. expect(e).toEqual({
  11. error: 'User with 1 not found.',
  12. });
  13. }
  14. });

您也可以使用 Promise 链. 在这种情况下,您可以利用done回调和done.fail发生错误. 以下是一些示例:

  1. // Good
  2. it('tests a promise', done => {
  3. promise
  4. .then(data => {
  5. expect(data).toBe(asExpected);
  6. })
  7. .then(done)
  8. .catch(done.fail);
  9. });
  10. // Good
  11. it('tests a promise rejection', done => {
  12. promise
  13. .then(done.fail)
  14. .catch(error => {
  15. expect(error).toBe(expectedError);
  16. })
  17. .then(done)
  18. .catch(done.fail);
  19. });
  20. // Bad (missing done callback)
  21. it('tests a promise', () => {
  22. promise.then(data => {
  23. expect(data).toBe(asExpected);
  24. });
  25. });
  26. // Bad (missing catch)
  27. it('tests a promise', done => {
  28. promise
  29. .then(data => {
  30. expect(data).toBe(asExpected);
  31. })
  32. .then(done);
  33. });
  34. // Bad (use done.fail in asynchronous tests)
  35. it('tests a promise', done => {
  36. promise
  37. .then(data => {
  38. expect(data).toBe(asExpected);
  39. })
  40. .then(done)
  41. .catch(fail);
  42. });
  43. // Bad (missing catch)
  44. it('tests a promise rejection', done => {
  45. promise
  46. .catch(error => {
  47. expect(error).toBe(expectedError);
  48. })
  49. .then(done);
  50. });

Manipulating Time

有时我们必须测试时间敏感的代码. 例如,每隔 X 秒或类似的时间运行一次重复发生的事件. 在这里,您将找到一些应对策略:

setTimeout() / setInterval() in application

如果应用程序本身正在等待一段时间,请模拟等待. 在 Jest 中,默认情况下完成操作 (另请参见Jest Timer Mocks ). 在 Karma 中,您可以使用Jasmine 模拟时钟 .

  1. const doSomethingLater = () => {
  2. setTimeout(() => {
  3. // do something
  4. }, 4000);
  5. };

在:

  1. it('does something', () => {
  2. doSomethingLater();
  3. jest.runAllTimers();
  4. expect(something).toBe('done');
  5. });

在业力中:

  1. it('does something', () => {
  2. jasmine.clock().install();
  3. doSomethingLater();
  4. jasmine.clock().tick(4000);
  5. expect(something).toBe('done');
  6. jasmine.clock().uninstall();
  7. });

Waiting in tests

有时,测试需要等待应用程序中的某些事情发生后才能继续. 避免使用setTimeout因为它会使等待的原因不清楚,如果在 Karma 中使用的时间大于零,则会减慢我们的测试套件的速度. 而是使用以下方法之一.

Promises and Ajax calls

注册处理程序函数以等待Promise被解决.

  1. const askTheServer = () => {
  2. return axios
  3. .get('/endpoint')
  4. .then(response => {
  5. // do something
  6. })
  7. .catch(error => {
  8. // do something else
  9. });
  10. };

在:

  1. it('waits for an Ajax call', async () => {
  2. await askTheServer()
  3. expect(something).toBe('done');
  4. });

在业力中:

  1. it('waits for an Ajax call', done => {
  2. askTheServer()
  3. .then(() => {
  4. expect(something).toBe('done');
  5. })
  6. .then(done)
  7. .catch(done.fail);
  8. });

如果您无法将处理程序注册到Promise ,例如因为它是在同步 Vue 生命周期挂钩中执行的,请查看waitFor帮助器,或者您可以刷新所有待处理的Promise

在:

  1. it('waits for an Ajax call', () => {
  2. synchronousFunction();
  3. jest.runAllTicks();
  4. expect(something).toBe('done');
  5. });

Vue rendering

要等到重新渲染 Vue 组件后,请使用等效的Vue.nextTick()vm.$nextTick() .

在:

  1. it('renders something', () => {
  2. wrapper.setProps({ value: 'new value' });
  3. return wrapper.vm.$nextTick().then(() => {
  4. expect(wrapper.text()).toBe('new value');
  5. });
  6. });

in Karma:

  1. it('renders something', done => {
  2. wrapper.setProps({ value: 'new value' });
  3. wrapper.vm
  4. .$nextTick()
  5. .then(() => {
  6. expect(wrapper.text()).toBe('new value');
  7. })
  8. .then(done)
  9. .catch(done.fail);
  10. });

Events

如果应用程序触发了您需要在测试中等待的事件,请注册一个包含断言的事件处理程序:

  1. it('waits for an event', done => {
  2. eventHub.$once('someEvent', eventHandler);
  3. someFunction();
  4. function eventHandler() {
  5. expect(something).toBe('done');
  6. done();
  7. }
  8. });

在 Jest 中,您还可以使用Promise

  1. it('waits for an event', () => {
  2. const eventTriggered = new Promise(resolve => eventHub.$once('someEvent', resolve));
  3. someFunction();
  4. return eventTriggered.then(() => {
  5. expect(something).toBe('done');
  6. });
  7. });

Ensuring that tests are isolated

测试通常以一种模式进行架构,该模式要求重复设置并破坏被测组件. 这是通过使用beforeEachafterEach挂钩完成的.

Example

  1. let wrapper;
  2. beforeEach(() => {
  3. wrapper = mount(Component);
  4. });
  5. afterEach(() => {
  6. wrapper.destroy();
  7. });

最初查看此内容时,您会怀疑该组件是在每次测试之前设置的,然后在以后进行了细分,从而在测试之间提供了隔离.

但是,这并不是完全正确的,因为destroy方法不会删除wrapper对象上所有已突变的东西. 对于功能组件,destroy 仅从文档中删除呈现的 DOM 元素.

In order to ensure that a clean wrapper object and DOM are being used in each test, the breakdown of the component should rather be performed as follows:

  1. afterEach(() => {
  2. wrapper.destroy();
  3. wrapper = null;
  4. });

另请参见有关destroyVue Test Utils 文档 .

Jest best practices

在 GitLab 13.2 中引入 .

Prefer toBe over toEqual when comparing primitive values

toBe toEqual匹配者. 由于toBe使用Object.is比较值,因此(默认情况下)比使用toEqual . 尽管后者最终将回退以利用Object.is获得原始值,但仅应在需要比较复杂对象的情况下使用它.

Examples:

  1. const foo = 1;
  2. // good
  3. expect(foo).toBe(1);
  4. // bad
  5. expect(foo).toEqual(1);

Prefer more befitting matchers

Jest 提供了有用的匹配器,例如toHaveLengthtoBeUndefined以使您的测试更具可读性并产生更易理解的错误消息. 查看他们的文档以获取匹配器完整列表 .

Examples:

  1. const arr = [1, 2];
  2. // prints:
  3. // Expected length: 1
  4. // Received length: 2
  5. expect(arr).toHaveLength(1);
  6. // prints:
  7. // Expected: 1
  8. // Received: 2
  9. expect(arr.length).toBe(1);
  10. // prints:
  11. // expect(received).toBe(expected) // Object.is equality
  12. // Expected: undefined
  13. // Received: "bar"
  14. const foo = 'bar';
  15. expect(foo).toBe(undefined);
  16. // prints:
  17. // expect(received).toBeUndefined()
  18. // Received: "bar"
  19. const foo = 'bar';
  20. expect(foo).toBeUndefined();

Avoid using toBeTruthy or toBeFalsy

Jest 还提供以下匹配器: toBeTruthytoBeFalsy . 我们不应该使用它们,因为它们会使测试变弱并产生假阳性结果.

例如,当someBoolean === null以及someBoolean === false时,会通过expect(someBoolean).toBeFalsy() .

Tricky toBeDefined matcher

Jest 有一个棘手的toBeDefined匹配器,它可能会产生假阳性测试. 因为它仅验证 undefined的给定值.

  1. // good
  2. expect(wrapper.find('foo').exists()).toBe(true);
  3. // bad
  4. // if finder returns null, the test will pass
  5. expect(wrapper.find('foo')).toBeDefined();

Avoid using setImmediate

尝试避免使用setImmediate . setImmediate是一个临时解决方案,可在 I / O 完成后运行您的回调. 而且它不是 Web API 的一部分,因此,我们在单元测试中以 NodeJS 环境为目标.

代替setImmediate ,使用jest.runAllTimersjest.runOnlyPendingTimers来运行暂挂计时器. 当代码中有setInterval时,后者很有用. 请记住:我们的 Jest 配置使用假计时器.

Factories

TBU

Mocking Strategies with Jest

Stubbing and Mocking

Jasmine 提供存根和模拟功能. 在 Karma 和 Jest 中,如何使用它有一些细微的差异.

存根或间谍通常是同义词. 在 Jest 中,使用.spyOn方法非常容易. 官方文档更具挑战性的部分是模拟,可用于功能甚至依赖项.

Manual module mocks

手动模拟用于模拟整个 Jest 环境中的模块. 这是一个功能非常强大的测试工具,它通过模拟出在我们的测试环境中不容易使用的模块来帮助简化单元测试.

警告:如果不应在每个规范中始终采用模拟,则不要使用手动模拟(即,仅少数规范需要使用). 而是考虑在相关规范文件中使用jest.mock(..) (或类似的jest.mock(..)功能).

Where should I put manual mocks?

Jest 通过将模拟放置在源模块旁边的__mocks__/目录(例如app/assets/javascripts/ide/__mocks__ )中来支持手动模块模拟. 不要这样 我们希望将所有与测试相关的代码保存在一个地方( spec/文件夹).

如果node_modules软件包需要手动模拟,请使用spec/frontend/__mocks__文件夹. 这是monaco-editor软件包Jest 模拟示例.

如果 CE 模块需要手动模拟,请将其放在spec/frontend/mocks/ce .

  • spec/frontend/mocks/ce文件将从app/assets/javascripts模拟相应的 CE 模块,从而镜像源模块的路径.
    • 示例: spec/frontend/mocks/ce/lib/utils/axios_utils将模拟模块~/lib/utils/axios_utils .
  • 我们尚不支持模拟 EE 模块.
  • 如果找到了不存在源模块的模拟,则测试套件将失败. 目前尚不支持”虚拟”模拟或与源模块没有 1 对 1 关联的模拟.

Manual mock examples

  • 模拟mocks/axios_utils此模拟很有用,因为我们不希望任何未经模拟的请求通过任何测试. 另外,我们能够注入一些测试助手,例如axios.waitForAll .
  • __mocks__/mousetrap/index.js此模拟很有用,因为该模块本身使用 webpack 可以理解的 AMD 格式,但与玩笑的环境不兼容. 该模拟不会删除任何行为,仅提供与 es6 兼容的包装器.
  • __mocks__/monaco-editor/index.js - This mock is helpful because the Monaco package is completely incompatible in a Jest environment. In fact, webpack requires a special loader to make it work. This mock simply makes this package consumable by Jest.

Keep mocks light

全局模拟会引入魔术,并且从技术上讲可以减少测试的覆盖范围. 当嘲笑被视为有利可图时:

  • 保持模拟简短而集中.
  • 请在模拟中留下为什么有必要的顶级评论.

Additional mocking techniques

请查阅官方的 Jest 文档 ,以获取有关可用模拟功能的完整概述.

Running Frontend Tests

要运行前端测试,您需要以下命令:

  • rake frontend:fixtures (re-)generates fixtures.
  • yarn test执行测试.
  • yarn jest执行 Jest 测试.

只要固定装置不变, yarn test就足够了(并节省了一些时间).

Live testing and focused testing – Jest

在测试套件上工作时,您可能希望以监视模式运行这些规范,因此它们会在每次保存时自动重新运行.

  1. # Watch and rerun all specs matching the name icon
  2. yarn jest --watch icon
  3. # Watch and rerun one specifc file
  4. yarn jest --watch path/to/spec/file.spec.js

您也可以在不使用--watch标志的情况下运行一些重点测试

  1. # Run specific jest file
  2. yarn jest ./path/to/local_spec.js
  3. # Run specific jest folder
  4. yarn jest ./path/to/folder/
  5. # Run all jest files which path contain term
  6. yarn jest term

Live testing and focused testing – Karma

业力允许类似的东西,但是成本更高.

yarn run karma-start运行 Karma 将编译 JavaScript 资产并在http://localhost:9876/上运行服务器,在该服务器上它将自动在连接到它的任何浏览器上运行测试. 您可以一次在多个浏览器上输入该 URL,以使其在每个浏览器上并行运行测试.

当 Karma 运行时,您所做的任何更改都会立即触发整个测试套件的重新编译和重新测试 ,因此您可以立即查看是否使用所做的更改破坏了测试. 您可以使用针对 Jasmine 的测试或排除测试(使用fdescribexdescribe )来使 Karma 仅在您使用特定功能时运行所需的测试,但是请确保在提交代码时删除这些指令.

通过使用参数--filter-spec或 short -f过滤运行测试,也可以仅在特定的文件夹或文件上运行 Karma:

  1. # Run all files
  2. yarn karma-start
  3. # Run specific spec files
  4. yarn karma-start --filter-spec profile/account/components/update_username_spec.js
  5. # Run specific spec folder
  6. yarn karma-start --filter-spec profile/account/components/
  7. # Run all specs which path contain vue_shared or vie
  8. yarn karma-start -f vue_shared -f vue_mr_widget

您还可以使用 glob 语法来匹配文件. 请记住在引号周围加上引号,否则您的 shell 可能会将其拆分为多个参数:

  1. # Run all specs named `file_spec` within the IDE subdirectory
  2. yarn karma -f 'spec/javascripts/ide/**/file_spec.js'

Frontend test fixtures

添加到 HAML 模板(在app/views/ )或向后端发出 Ajax 请求的代码具有要求后端提供 HTML 或 JSON 的测试. 这些测试的夹具位于:

  • spec/frontend/fixtures/ ,用于在 CE 中运行测试.
  • ee/spec/frontend/fixtures/ ,用于在 EE 中运行测试.

灯具文件位于:

以下是适用于 Karma 和 Jest 的测试示例:

  1. it('makes a request', () => {
  2. const responseBody = getJSONFixture('some/fixture.json'); // loads spec/frontend/fixtures/some/fixture.json
  3. axiosMock.onGet(endpoint).reply(200, responseBody);
  4. myButton.click();
  5. // ...
  6. });
  7. it('uses some HTML element', () => {
  8. loadFixtures('some/page.html'); // loads spec/frontend/fixtures/some/page.html and adds it to the DOM
  9. const element = document.getElementById('#my-id');
  10. // ...
  11. });

HTML 和 JSON 固定装置是使用 RSpec 从后端视图和控制器生成的(请参阅spec/frontend/fixtures/*.rb ).

对于每个灯具, response变量的内容存储在输出文件中. 如果测试标记为type: :requesttype: :controller则此变量将自动设置. 可以使用bin/rake frontend:fixtures命令来重新生成bin/rake frontend:fixtures但是您也可以单独生成它们,例如bin/rspec spec/frontend/fixtures/merge_requests.rb . 创建新的固定装置时,通常有必要在(ee/)spec/controllers/(ee/)spec/requests/查看端点的相应测试.

Data-driven tests

RSpec 的参数化测试类似,Jest 支持以下数据驱动的测试:

这些对于减少测试中的重复很有用. 每个选项都可以采用数据值数组或带标签的模板文字.

例如:

  1. // function to test
  2. const icon = status => status ? 'pipeline-passed' : 'pipeline-failed'
  3. const message = status => status ? 'pipeline-passed' : 'pipeline-failed'
  4. // test with array block
  5. it.each([
  6. [false, 'pipeline-failed'],
  7. [true, 'pipeline-passed']
  8. ])('icon with %s will return %s',
  9. (status, icon) => {
  10. expect(renderPipeline(status)).toEqual(icon)
  11. }
  12. );
  1. // test suite with tagged template literal block
  2. describe.each`
  3. status | icon | message ${false} | ${'pipeline-failed'} | ${'Pipeline failed - boo-urns'} ${true} | ${'pipeline-passed'} | ${'Pipeline succeeded - win!'} `('pipeline component', ({ status, icon, message }) => {
  4. it(`returns icon ${icon} with status ${status}`, () => {
  5. expect(icon(status)).toEqual(message)
  6. })
  7. it(`returns message ${message} with status ${status}`, () => {
  8. expect(message(status)).toEqual(message)
  9. })
  10. });

Gotchas

RSpec errors due to JavaScript

默认情况下,RSpec 单元测试不会在无头浏览器中运行 JavaScript,而仅依赖于检查 rails 生成的 HTML.

如果集成测试依赖 JavaScript 才能正确运行,则需要确保将规范配置为在运行测试时启用 JavaScript. 如果不这样做,您会看到规范运行器发出的模糊错误消息.

To enable a JavaScript driver in an rspec test, add :js to the individual spec or the context block containing multiple specs that need JavaScript enabled:

  1. # For one spec
  2. it 'presents information about abuse report', :js do
  3. # assertions...
  4. end
  5. describe "Admin::AbuseReports", :js do
  6. it 'presents information about abuse report' do
  7. # assertions...
  8. end
  9. it 'shows buttons for adding to abuse report' do
  10. # assertions...
  11. end
  12. end

Overview of Frontend Testing Levels

有关前端测试级别的主要信息,请参见” 测试级别”页面 .

与前端开发相关的测试可以在以下位置找到:

  • spec/javascripts/ ,用于业力测试
  • spec/frontend/ ,用于 Is 测试
  • spec/features/ ,用于 RSpec 测试

RSpec 运行完整的功能测试 ,而 Jest 和 Karma 目录包含前端单元测试前端组件测试前端集成测试 .

spec/javascripts/所有测试最终都将迁移到spec/frontend/ (另请参见#52483 ).

在 2018 年 5 月之前, features/还包含 Spinach 运行的功能测试. 这些测试已于 2018 年 5 月从代码库中删除( #23036 ).

另请参阅有关测试 Vue 组件的说明 .

Test helpers

Vuex Helper: testAction

根据官方文档 ,我们提供了一个帮助程序,可以简化测试操作:

  1. testAction(
  2. actions.actionName, // action
  3. { }, // params to be passed to action
  4. state, // state
  5. [
  6. { type: types.MUTATION},
  7. { type: types.MUTATION_1, payload: {}},
  8. ], // mutations committed
  9. [
  10. { type: 'actionName', payload: {}},
  11. { type: 'actionName1', payload: {}},
  12. ] // actions dispatched
  13. done,
  14. );

spec/javascripts/ide/stores/actions_spec.jsspec/javascripts/ide/stores/actions_spec.js .

Wait until Axios requests finish

The Axios Utils mock module located in spec/frontend/mocks/ce/lib/utils/axios_utils.js contains two helper methods for Jest tests that spawn HTTP requests. These are very useful if you don’t have a handle to the request’s Promise, for example when a Vue component does a request as part of its life cycle.

  • waitFor(url, callback) :对url的请求完成后(成功或失败)运行callback .
  • waitForAll(callback) :所有未完成的请求完成后,运行callback . 如果没有待处理的请求,则在下一个刻度上运行callback .

这两个函数运行callback (使用请求结束后的下一个滴答setImmediate()以允许任何.then().catch()处理程序来运行.

Testing with older browsers

某些回归仅影响特定的浏览器版本. 我们可以按照以下步骤使用 Firefox 或 BrowserStack 在特定的浏览器中安装和测试:

BrowserStack

BrowserStack可以测试 1200 多种移动设备和浏览器. 您可以直接通过实时应用程序使用它,也可以安装chrome 扩展程序以便于访问. 使用保存在 GitLab 共享 1Password 帐户Engineering库中的凭据登录到 BrowserStack.

Firefox

macOS

您可以从发布的 FTP 服务器https://ftp.mozilla.org/pub/firefox/releases/下载任何较旧版本的 Firefox:

  1. 从网站上选择一个版本,在本例中为50.0.1 .
  2. 转到 mac 文件夹.
  3. 选择您喜欢的语言,您将在其中找到 DMG 软件包并下载.
  4. 将应用程序拖放到”应用Applications文件夹以外的任何其他文件夹中.
  5. 将应用程序重命名为Firefox_Old .
  6. 将应用程序移至”应用Applications文件夹.
  7. 打开终端,然后运行/Applications/Firefox_Old.app/Contents/MacOS/firefox-bin -profilemanager以创建特定于该 Firefox 版本的新配置文件.
  8. 创建配置文件后,请退出应用程序,然后像平常一样再次运行它. 现在,您可以使用较旧的 Firefox 版本.

Return to Testing documentation