summaryrefslogtreecommitdiff
path: root/doc/development/testing_guide/frontend_testing.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/testing_guide/frontend_testing.md')
-rw-r--r--doc/development/testing_guide/frontend_testing.md119
1 files changed, 79 insertions, 40 deletions
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 730f8d5ad7d..28fe63f1fb4 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Frontend testing standards and style guidelines
There are two types of test suites you'll encounter while developing frontend code
@@ -24,7 +30,6 @@ We have started to migrate frontend tests to the [Jest](https://jestjs.io) testi
Jest tests can be found in `/spec/frontend` and `/ee/spec/frontend` in EE.
-NOTE: **Note:**
Most examples have a Jest and Karma example. See the Karma examples only as explanation to what's going on in the code, should you stumble over some use cases during your discovery. The Jest examples are the one you should follow.
## Karma test suite
@@ -110,6 +115,37 @@ describe('Component', () => {
Remember that the performance of each test depends on the environment.
+### Timout error due to async components
+
+If your component is fetching some other components asynchroneously based on some conditions, it might happen so that your Jest suite for this component will become flaky timing out from time to time.
+
+```javascript
+// ide.vue
+export default {
+ components: {
+ 'error-message': () => import('./error_message.vue'),
+ 'gl-button': () => import('@gitlab/ui/src/components/base/button/button.vue'),
+ ...
+};
+```
+
+To address this issue, you can "help" Jest by stubbing the async components so that Jest would not need to fetch those asynchroneously at the run-time.
+
+```javascript
+// ide_spec.js
+import { GlButton } from '@gitlab/ui';
+import ErrorMessage from '~/ide/components/error_message.vue';
+...
+return shallowMount(ide, {
+ ...
+ stubs: {
+ ErrorMessage,
+ GlButton,
+ ...
+ },
+})
+```
+
## What and how to test
Before jumping into more gritty details about Jest-specific workflows like mocks and spies, we should briefly cover what to test with Jest.
@@ -198,47 +234,33 @@ Following you'll find some general common practices you will find as part of our
When it comes to querying DOM elements in your tests, it is best to uniquely and semantically target
the element.
-Preferentially, this is done by targeting text the user actually sees using [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro).
+Preferentially, this is done by targeting what the user actually sees using [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro).
When selecting by text it is best to use [`getByRole` or `findByRole`](https://testing-library.com/docs/dom-testing-library/api-queries#byrole)
as these enforce accessibility best practices as well. The examples below demonstrate the order of preference.
-Sometimes this cannot be done feasibly. In these cases, adding test attributes to simplify the
-selectors might be the best option.
+When writing Vue component unit tests, it can be wise to query children by component, so that the unit test can focus on comprehensive value coverage
+rather than dealing with the complexity of a child component's behavior.
+
+Sometimes, neither of the above are feasible. In these cases, adding test attributes to simplify the selectors might be the best option. A list of
+possible selectors include:
- A semantic attribute like `name` (also verifies that `name` was setup properly)
- A `data-testid` attribute ([recommended by maintainers of `@vue/test-utils`](https://github.com/vuejs/vue-test-utils/issues/1498#issuecomment-610133465))
- a Vue `ref` (if using `@vue/test-utils`)
```javascript
-import { mount, shallowMount } from '@vue/test-utils'
import { getByRole, getByText } from '@testing-library/dom'
-let wrapper
-let el
-
-const createComponent = (mountFn = shallowMount) => {
- wrapper = mountFn(Component)
- el = wrapper.vm.$el // reference to the container element
-}
-
-beforeEach(() => {
- createComponent()
-})
-
-
+// In this example, `wrapper` is a `@vue/test-utils` wrapper returned from `mount` or `shallowMount`.
it('exists', () => {
- // Best
-
- // NOTE: both mount and shallowMount work as long as a DOM element is available
- // Finds a properly formatted link with an accessible name of "Click Me"
- getByRole(el, 'link', { name: /Click Me/i })
- getByRole(el, 'link', { name: 'Click Me' })
- // Finds any element with the text "Click Me"
- getByText(el, 'Click Me')
- // Regex is also available
- getByText(el, /Click Me/i)
-
- // Good
+ // Best (especially for integration tests)
+ getByRole(wrapper.element, 'link', { name: /Click Me/i })
+ getByRole(wrapper.element, 'link', { name: 'Click Me' })
+ getByText(wrapper.element, 'Click Me')
+ getByText(wrapper.element, /Click Me/i)
+
+ // Good (especially for unit tests)
+ wrapper.find(FooComponent);
wrapper.find('input[name=foo]');
wrapper.find('[data-testid="foo"]');
wrapper.find({ ref: 'foo'});
@@ -249,14 +271,6 @@ it('exists', () => {
wrapper.find('.qa-foo-component');
wrapper.find('[data-qa-selector="foo"]');
});
-
-// Good
-it('exists', () => {
- wrapper.find(FooComponent);
- wrapper.find('input[name=foo]');
- wrapper.find('[data-testid="foo"]');
- wrapper.find({ ref: 'foo'});
-});
```
It is not recommended that you add `.js-*` classes just for testing purposes. Only do this if there are no other feasible options available.
@@ -327,7 +341,6 @@ it('tests a promise rejection', async () => {
You can also simply return a promise from the test function.
-NOTE: **Note:**
Using the `done` and `done.fail` callbacks is discouraged when working with
promises. They should only be used when testing callback-based code.
@@ -777,7 +790,7 @@ While you work on a test suite, you may want to run these specs in watch mode, s
# Watch and rerun all specs matching the name icon
yarn jest --watch icon
-# Watch and rerun one specifc file
+# Watch and rerun one specific file
yarn jest --watch path/to/spec/file.spec.js
```
@@ -917,6 +930,32 @@ it.each([
);
```
+**Note**: only use template literal block if pretty print is **not** needed for spec output. For example, empty strings, nested objects etc.
+
+For example, when testing the difference between an empty search string and a non-empty search string, the use of the array block syntax with the pretty print option would be preferred. That way the differences between an empty string e.g. `''` and a non-empty string e.g. `'search string'` would be visible in the spec output. Whereas with a template literal block, the empty string would be shown as a space, which could lead to a confusing developer experience
+
+```javascript
+// bad
+it.each`
+ searchTerm | expected
+ ${''} | ${{ issue: { users: { nodes: [] } } }}
+ ${'search term'} | ${{ issue: { other: { nested: [] } } }}
+`('when search term is $searchTerm, it returns $expected', ({ searchTerm, expected }) => {
+ expect(search(searchTerm)).toEqual(expected)
+});
+
+// good
+it.each([
+ ['', { issue: { users: { nodes: [] } } }],
+ ['search term', { issue: { other: { nested: [] } } }],
+])('when search term is %p, expect to return %p',
+ (searchTerm, expected) => {
+ expect(search(searchTerm)).toEqual(expected)
+ }
+);
+
+```
+
```javascript
// test suite with tagged template literal block
describe.each`