diff options
Diffstat (limited to 'doc/development/testing_guide')
7 files changed, 266 insertions, 88 deletions
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index ee8401e08d4..828e9925d46 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -86,7 +86,7 @@ a parent context. Examples of these are: - `:clean_gitlab_redis_cache` which provides a clean Redis cache to the examples. - `:request_store` which provides a request store to the examples. -Obviously we should reduce test dependencies, and avoiding +We should reduce test dependencies, and avoiding capabilities also reduces the amount of set-up needed. `:js` is particularly important to avoid. This must only be used if the feature @@ -350,12 +350,140 @@ writing one](testing_levels.md#consider-not-writing-a-system-test)! - It's ok to look for DOM elements, but don't abuse it, because it makes the tests more brittle -#### Debugging Capybara +#### UI testing -Sometimes you may need to debug Capybara tests by observing browser behavior. +When testing the UI, write tests that simulate what a user sees and how they interact with the UI. +This means preferring Capybara's semantic methods and avoiding querying by IDs, classes, or attributes. + +The benefits of testing in this way are that: + +- It ensures all interactive elements have an [accessible name](../fe_guide/accessibility.md#provide-accessible-names-for-screen-readers). +- It is more readable, as it uses more natural language. +- It is less brittle, as it avoids querying by IDs, classes, and attributes, which are not visible to the user. + +We strongly recommend that you query by the element's text label instead of by ID, class name, or `data-testid`. + +If needed, you can scope interactions within a specific area of the page by using `within`. +As you will likely be scoping to an element such as a `div`, which typically does not have a label, +you may use a `data-testid` selector in this case. + +##### Actions + +Where possible, use more specific [actions](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Actions), such as the ones below. + +```ruby +# good +click_button 'Submit review' + +click_link 'UI testing docs' + +fill_in 'Search projects', with: 'gitlab' # fill in text input with text + +select 'Last updated', from: 'Sort by' # select an option from a select input + +check 'Checkbox label' +uncheck 'Checkbox label' + +choose 'Radio input label' + +attach_file('Attach a file', '/path/to/file.png') + +# bad - interactive elements must have accessible names, so +# we should be able to use one of the specific actions above +find('.group-name', text: group.name).click +find('.js-show-diff-settings').click +find('[data-testid="submit-review"]').click +find('input[type="checkbox"]').click +find('.search').native.send_keys('gitlab') +``` + +##### Finders + +Where possible, use more specific [finders](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Finders), such as the ones below. + +```ruby +# good +find_button 'Submit review' +find_button 'Submit review', disabled: true + +find_link 'UI testing docs' +find_link 'UI testing docs', href: docs_url + +find_field 'Search projects' +find_field 'Search projects', with: 'gitlab' # find the input field with text +find_field 'Search projects', disabled: true +find_field 'Checkbox label', checked: true +find_field 'Checkbox label', unchecked: true + +# acceptable when finding a element that is not a button, link, or field +find('[data-testid="element"]') +``` + +##### Matchers + +Where possible, use more specific [matchers](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/RSpecMatchers), such as the ones below. + +```ruby +# good +expect(page).to have_button 'Submit review' +expect(page).to have_button 'Submit review', disabled: true +expect(page).to have_button 'Notifications', class: 'is-checked' # assert the "Notifications" GlToggle is checked + +expect(page).to have_link 'UI testing docs' +expect(page).to have_link 'UI testing docs', href: docs_url # assert the link has an href + +expect(page).to have_field 'Search projects' +expect(page).to have_field 'Search projects', disabled: true +expect(page).to have_field 'Search projects', with: 'gitlab' # assert the input field has text + +expect(page).to have_checked_field 'Checkbox label' +expect(page).to have_unchecked_field 'Radio input label' + +expect(page).to have_select 'Sort by' +expect(page).to have_select 'Sort by', selected: 'Last updated' # assert the option is selected +expect(page).to have_select 'Sort by', options: ['Last updated', 'Created date', 'Due date'] # assert an exact list of options +expect(page).to have_select 'Sort by', with_options: ['Created date', 'Due date'] # assert a partial list of options + +expect(page).to have_text 'Some paragraph text.' +expect(page).to have_text 'Some paragraph text.', exact: true # assert exact match + +expect(page).to have_current_path 'gitlab/gitlab-test/-/issues' + +expect(page).to have_title 'Not Found' + +# acceptable when a more specific matcher above is not possible +expect(page).to have_css 'h2', text: 'Issue title' +expect(page).to have_css 'p', text: 'Issue description', exact: true +expect(page).to have_css '[data-testid="weight"]', text: 2 +expect(page).to have_css '.atwho-view ul', visible: true +``` + +##### Other useful methods + +After you retrieve an element using a [finder method](#finders), you can invoke a number of +[element methods](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Element) +on it, such as `hover`. + +Capybara tests also have a number of [session methods](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Session) available, such as `accept_confirm`. + +Some other useful methods are shown below: + +```ruby +refresh # refresh the page + +send_keys([:shift, 'i']) # press Shift+I keys to go to the Issues dashboard page + +current_window.resize_to(1000, 1000) # resize the window + +scroll_to(find_field('Comment')) # scroll to an element +``` + +You can also find a number of GitLab custom helpers in the `spec/support/helpers/` directory. #### Live debug +Sometimes you may need to debug Capybara tests by observing browser behavior. + You can pause Capybara and view the website on the browser by using the `live_debug` method in your spec. The current page is automatically opened in your default browser. diff --git a/doc/development/testing_guide/end_to_end/beginners_guide.md b/doc/development/testing_guide/end_to_end/beginners_guide.md index d60b780eeea..29f6c93d65a 100644 --- a/doc/development/testing_guide/end_to_end/beginners_guide.md +++ b/doc/development/testing_guide/end_to_end/beginners_guide.md @@ -332,12 +332,13 @@ can see it. ## Run the spec -Before running the spec, confirm: +Before running the spec, make sure that: -- The GDK is installed. -- The GDK is running on port 3000 locally. +- GDK is installed. +- GDK is running locally on port 3000. - No additional [RSpec metadata tags](rspec_metadata_tests.md) have been applied. - Your working directory is `qa/` within your GDK GitLab installation. +- Your GitLab instance-level settings are default. If you changed the default settings, some tests might have unexpected results. To run the spec, run the following command: diff --git a/doc/development/testing_guide/end_to_end/best_practices.md b/doc/development/testing_guide/end_to_end/best_practices.md index 2b4212a0172..15520d8a6b1 100644 --- a/doc/development/testing_guide/end_to_end/best_practices.md +++ b/doc/development/testing_guide/end_to_end/best_practices.md @@ -223,6 +223,15 @@ In summary: - **Do**: Split tests across separate files, unless the tests share expensive setup. - **Don't**: Put new tests in an existing file without considering the impact on parallelization. +## `let` variables vs instance variables + +By default, follow the [testing best practices](../best_practices.md#subject-and-let-variables) when using `let` +or instance variables. However, in end-to-end tests, set-ups such as creating resources are expensive. +If you use `let` to store a resource, it will be created for each example separately. +If the resource can be shared among multiple examples, use an instance variable in the `before(:all)` +block instead of `let` to save run time. +When the variable cannot be shared by multiple examples, use `let`. + ## Limit the use of the UI in `before(:context)` and `after` hooks Limit the use of `before(:context)` hooks to perform setup tasks with only API calls, diff --git a/doc/development/testing_guide/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md index d981f9bcbba..e6da4771e55 100644 --- a/doc/development/testing_guide/end_to_end/index.md +++ b/doc/development/testing_guide/end_to_end/index.md @@ -22,13 +22,13 @@ a black-box testing framework for the API and the UI. ### Testing nightly builds We run scheduled pipelines each night to test nightly builds created by Omnibus. -You can find these nightly pipelines at `https://gitlab.com/gitlab-org/quality/nightly/pipelines` +You can find these pipelines at <https://gitlab.com/gitlab-org/quality/nightly/pipelines> (need Developer access permissions). Results are reported in the `#qa-nightly` Slack channel. ### Testing staging We run scheduled pipelines each night to test staging. -You can find these nightly pipelines at `https://gitlab.com/gitlab-org/quality/staging/pipelines` +You can find these pipelines at <https://gitlab.com/gitlab-org/quality/staging/pipelines> (need Developer access permissions). Results are reported in the `#qa-staging` Slack channel. ### Testing code in merge requests @@ -36,64 +36,76 @@ You can find these nightly pipelines at `https://gitlab.com/gitlab-org/quality/s #### Using the `package-and-qa` job It is possible to run end-to-end tests for a merge request, eventually being run in -a pipeline in the [`gitlab-qa-mirror`](https://gitlab.com/gitlab-org/gitlab-qa-mirror/) project, -by triggering the `package-and-qa` manual action in the `test` stage (not +a pipeline in the [`gitlab-org/gitlab-qa-mirror`](https://gitlab.com/gitlab-org/gitlab-qa-mirror) project, +by triggering the `package-and-qa` manual action in the `qa` stage (not available for forks). -**This runs end-to-end tests against a custom CE and EE (with an Ultimate license) -Omnibus package built from your merge request's changes.** +**This runs end-to-end tests against a custom EE (with an Ultimate license) +Docker image built from your merge request's changes.** -Manual action that starts end-to-end tests is also available in merge requests -in [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab). - -Below you can read more about how to use it and how does it work. +Manual action that starts end-to-end tests is also available +in [`gitlab-org/omnibus-gitlab` merge requests](https://docs.gitlab.com/omnibus/build/team_member_docs.html#i-have-an-mr-in-the-omnibus-gitlab-project-and-want-a-package-or-docker-image-to-test-it). #### How does it work? -Currently, we are using _multi-project pipeline_-like approach to run QA +Currently, we are using _multi-project pipeline_-like approach to run end-to-end pipelines. ```mermaid -graph LR - A1 -.->|1. Triggers an omnibus-gitlab-mirror pipeline and wait for it to be done| A2 - B2[`Trigger-qa` stage<br>`Trigger:qa-test` job] -.->|2. Triggers a gitlab-qa-mirror pipeline and wait for it to be done| A3 - -subgraph "gitlab-foss/gitlab pipeline" - A1[`test` stage<br>`package-and-qa` job] +graph TB + A1 -.->|once done, can be triggered| A2 + A2 -.->|1. Triggers an `omnibus-gitlab-mirror` pipeline<br>and wait for it to be done| B1 + B2[`Trigger-qa` stage<br>`Trigger:qa-test` job] -.->|2. Triggers a `gitlab-qa-mirror` pipeline<br>and wait for it to be done| C1 + +subgraph "`gitlab-org/gitlab` pipeline" + A1[`build-images` stage<br>`build-qa-image` and `build-assets-image` jobs] + A2[`qa` stage<br>`package-and-qa` job] end -subgraph "omnibus-gitlab pipeline" - A2[`Trigger-docker` stage<br>`Trigger:gitlab-docker` job] -->|once done| B2 +subgraph "`gitlab-org/build/omnibus-gitlab-mirror` pipeline" + B1[`Trigger-docker` stage<br>`Trigger:gitlab-docker` job] -->|once done| B2 end -subgraph "gitlab-qa-mirror pipeline" - A3>QA jobs run] -.->|3. Reports back the pipeline result to the `package-and-qa` job<br>and post the result on the original commit tested| A1 +subgraph "`gitlab-org/gitlab-qa-mirror` pipeline" + C1>End-to-end jobs run] end ``` -1. Developer triggers a manual action, that can be found in GitLab merge - requests. This starts a chain of pipelines in multiple projects. - -1. The script being executed triggers a pipeline in - [Omnibus GitLab Mirror](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) - and waits for the resulting status. We call this a _status attribution_. - -1. GitLab packages are being built in the [Omnibus GitLab Mirror](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) - pipeline. Packages are then pushed to its Container Registry. +1. In the [`gitlab-org/gitlab` pipeline](https://gitlab.com/gitlab-org/gitlab): + 1. Developer triggers the `package-and-qa` manual action (available once the `build-qa-image` and + `build-assets-image` jobs are done), that can be found in GitLab merge + requests. This starts a chain of pipelines in multiple projects. + 1. The script being executed triggers a pipeline in + [`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) + and polls for the resulting status. We call this a _status attribution_. -1. When packages are ready, and available in the registry, a final step in the - [Omnibus GitLab Mirror](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) pipeline, triggers a new - GitLab QA pipeline (those with access can view them at `https://gitlab.com/gitlab-org/gitlab-qa-mirror/pipelines`). It also waits for a resulting status. +1. In the [`gitlab-org/build/omnibus-gitlab-mirror` pipeline](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror): + 1. Docker image is being built and pushed to its Container Registry. + 1. Finally, the `Trigger:qa-test` job triggers a new end-to-end pipeline in + [`gitlab-org/gitlab-qa-mirror`](https://gitlab.com/gitlab-org/gitlab-qa-mirror/pipelines) and polls for the resulting status. -1. GitLab QA pulls images from the registry, spins-up containers and runs tests - against a test environment that has been just orchestrated by the `gitlab-qa` - tool. +1. In the [`gitlab-org/gitlab-qa-mirror` pipeline](https://gitlab.com/gitlab-org/gitlab-qa-mirror): + 1. Container for the Docker image stored in the [`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) registry is spun-up. + 1. End-to-end tests are run with the `gitlab-qa` executable, which spin up a container for the end-to-end image from the [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) registry. -1. The result of the GitLab QA pipeline is being - propagated upstream, through Omnibus, back to the GitLab merge request. +1. The result of the [`gitlab-org/gitlab-qa-mirror` pipeline](https://gitlab.com/gitlab-org/gitlab-qa-mirror) is being + propagated upstream (through polling from upstream pipelines), through [`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror), back to the [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) merge request. Please note, we plan to [add more specific information](https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/156) -about the tests included in each job/scenario that runs in `gitlab-qa-mirror`. +about the tests included in each job/scenario that runs in `gitlab-org/gitlab-qa-mirror`. + +NOTE: +You may have noticed that we use `gitlab-org/build/omnibus-gitlab-mirror` instead of +`gitlab-org/omnibus-gitlab`, and `gitlab-org/gitlab-qa-mirror` instead of `gitlab-org/gitlab-qa`. +This is due to technical limitations in the GitLab permission model: the ability to run a pipeline +against a protected branch is controlled by the ability to push/merge to this branch. +This means that for developers to be able to trigger a pipeline for the default branch in +`gitlab-org/omnibus-gitlab`/`gitlab-org/gitlab-qa`, they would need to have Maintainer permission in those projects. +For security reasons and implications, we couldn't open up the default branch to all the Developers. +Hence we created these mirrors where Developers and Maintainers are allowed to push/merge to the default branch. +This problem was discovered in <https://gitlab.com/gitlab-org/gitlab-qa/-/issues/63#note_107175160> and the "mirror" +work-around was suggested in <https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4717>. +A feature proposal to segregate access control regarding running pipelines from ability to push/merge was also created at <https://gitlab.com/gitlab-org/gitlab/-/issues/24585>. #### With Pipeline for Merged Results @@ -160,9 +172,9 @@ See [Review Apps](../review_apps.md) for more details about Review Apps. ## How do I run the tests? If you are not [testing code in a merge request](#testing-code-in-merge-requests), -there are two main options for running the tests. If you simply want to run -the existing tests against a live GitLab instance or against a pre-built Docker image -you can use the [GitLab QA orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md). See also [examples +there are two main options for running the tests. If you want to run +the existing tests against a live GitLab instance or against a pre-built Docker image, +use the [GitLab QA orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md). See also [examples of the test scenarios you can run via the orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#examples). On the other hand, if you would like to run against a local development GitLab diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md index f9c13d5dd67..22ddae6e836 100644 --- a/doc/development/testing_guide/end_to_end/style_guide.md +++ b/doc/development/testing_guide/end_to_end/style_guide.md @@ -122,7 +122,7 @@ avoid confusion or make the code more readable. For example, if a page object is named `New`, it could be confusing to name the block argument `new` because that is used to instantiate objects, so `new_page` would be acceptable. -We chose not to simply use `page` because that would shadow the +We chose not to use `page` because that would shadow the Capybara DSL, potentially leading to confusion and bugs. ### Examples diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md index 126d8725c21..a9af8f03d63 100644 --- a/doc/development/testing_guide/flaky_tests.md +++ b/doc/development/testing_guide/flaky_tests.md @@ -108,7 +108,7 @@ For instance `RETRIES=1 bin/rspec ...` would retry the failing examples once. #### PhantomJS / WebKit related issues -- Memory is through the roof! (TL;DR: Load images but block images requests!): <https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/12003> +- Memory is through the roof! (Load images but block images requests!): <https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/12003> #### Capybara expectation times out diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 9facca10142..7289e66a045 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -55,8 +55,8 @@ which have to be stubbed. - Because Jest runs in a Node.js environment, it uses [jsdom](https://github.com/jsdom/jsdom) by default. See also its [limitations](#limitations-of-jsdom) below. - Jest does not have access to Webpack loaders or aliases. The aliases used by Jest are defined in its [own configuration](https://gitlab.com/gitlab-org/gitlab/blob/master/jest.config.js). -- All calls to `setTimeout` and `setInterval` are mocked away. See also [Jest Timer Mocks](https://jestjs.io/docs/en/timer-mocks). -- `rewire` is not required because Jest supports mocking modules. See also [Manual Mocks](https://jestjs.io/docs/en/manual-mocks). +- All calls to `setTimeout` and `setInterval` are mocked away. See also [Jest Timer Mocks](https://jestjs.io/docs/timer-mocks). +- `rewire` is not required because Jest supports mocking modules. See also [Manual Mocks](https://jestjs.io/docs/manual-mocks). - No [context object](https://jasmine.github.io/tutorials/your_first_suite#section-The_%3Ccode%3Ethis%3C/code%3E_keyword) is passed to tests in Jest. This means sharing `this.something` between `beforeEach()` and `it()` for example does not work. Instead you should declare shared variables in the context that they are needed (via `const` / `let`). @@ -78,7 +78,7 @@ See also the issue for [support running Jest tests in browsers](https://gitlab.c ### Debugging Jest tests -Running `yarn jest-debug` runs Jest in debug mode, allowing you to debug/inspect as described in the [Jest docs](https://jestjs.io/docs/en/troubleshooting#tests-are-failing-and-you-don-t-know-why). +Running `yarn jest-debug` runs Jest in debug mode, allowing you to debug/inspect as described in the [Jest docs](https://jestjs.io/docs/troubleshooting#tests-are-failing-and-you-don-t-know-why). ### Timeout error @@ -104,6 +104,15 @@ describe('Component', () => { Remember that the performance of each test depends on the environment. +### Test-specific stylesheets + +To help facilitate RSpec integration tests we have two test-specific stylesheets. These can be used to do things like disable animations to improve test speed, or to make elements visible when they need to be targeted by Capybara click events: + +- `app/assets/stylesheets/disable_animations.scss` +- `app/assets/stylesheets/test_environment.scss` + +Because the test environment should match the production environment as much as possible, use these minimally and only add to them when necessary. + ## 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. @@ -212,8 +221,8 @@ When it comes to querying DOM elements in your tests, it is best to uniquely and the element. 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/queries/byrole/) -as these enforce accessibility best practices as well. The examples below demonstrate the order of preference. +When selecting by text it is best to use the [`byRole`](https://testing-library.com/docs/queries/byrole) query +as it helps enforce accessibility best practices. `findByRole` and the other [DOM Testing Library queries](https://testing-library.com/docs/queries/about) are available when using [`shallowMountExtended` or `mountExtended`](#shallowmountextended-and-mountextended). 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. @@ -223,25 +232,27 @@ 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)) - optionally combined with [`findByTestId`](#extendedwrapper-and-findbytestid) + optionally combined with [`shallowMountExtended` or `mountExtended`](#shallowmountextended-and-mountextended) - a Vue `ref` (if using `@vue/test-utils`) ```javascript -import { getByRole, getByText } from '@testing-library/dom' +import { shallowMountExtended } from 'helpers/vue_test_utils_helper' + +const wrapper = shallowMountExtended(ExampleComponent); // In this example, `wrapper` is a `@vue/test-utils` wrapper returned from `mount` or `shallowMount`. it('exists', () => { // 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) + wrapper.findByRole('link', { name: /Click Me/i }) + wrapper.findByRole('link', { name: 'Click Me' }) + wrapper.findByText('Click Me') + wrapper.findByText(/Click Me/i) // Good (especially for unit tests) wrapper.find(FooComponent); wrapper.find('input[name=foo]'); wrapper.find('[data-testid="my-foo-id"]'); - wrapper.findByTestId('my-foo-id'); // with the extendedWrapper utility – check below + wrapper.findByTestId('my-foo-id'); // with shallowMountExtended or mountExtended – check below wrapper.find({ ref: 'foo'}); // Bad @@ -376,7 +387,7 @@ Sometimes we have to test time-sensitive code. For example, recurring events tha If the application itself is waiting for some time, mock await the waiting. In Jest this is already [done by default](https://gitlab.com/gitlab-org/gitlab/blob/a2128edfee799e49a8732bfa235e2c5e14949c68/jest.config.js#L47) -(see also [Jest Timer Mocks](https://jestjs.io/docs/en/timer-mocks)). In Karma you can use the +(see also [Jest Timer Mocks](https://jestjs.io/docs/timer-mocks)). In Karma you can use the [Jasmine mock clock](https://jasmine.github.io/api/2.9/Clock.html). ```javascript @@ -564,9 +575,9 @@ This is however not entirely true as the `destroy` method does not remove everyt #### Prefer `toBe` over `toEqual` when comparing primitive values -Jest has [`toBe`](https://jestjs.io/docs/en/expect#tobevalue) and -[`toEqual`](https://jestjs.io/docs/en/expect#toequalvalue) matchers. -As [`toBe`](https://jestjs.io/docs/en/expect#tobevalue) uses +Jest has [`toBe`](https://jestjs.io/docs/expect#tobevalue) and +[`toEqual`](https://jestjs.io/docs/expect#toequalvalue) matchers. +As [`toBe`](https://jestjs.io/docs/expect#tobevalue) uses [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) to compare values, it's faster (by default) than using `toEqual`. While the latter eventually falls back to leverage [`Object.is`](https://github.com/facebook/jest/blob/master/packages/expect/src/jasmineUtils.ts#L91), @@ -588,7 +599,7 @@ expect(foo).toBe(1); Jest provides useful matchers like `toHaveLength` or `toBeUndefined` to make your tests more readable and to produce more understandable error messages. Check their docs for the -[full list of matchers](https://jestjs.io/docs/en/expect#methods). +[full list of matchers](https://jestjs.io/docs/expect#methods). Examples: @@ -719,7 +730,7 @@ TBU Jasmine provides stubbing and mocking capabilities. There are some subtle differences in how to use it within Karma and Jest. Stubs or spies are often used synonymously. In Jest it's quite easy thanks to the `.spyOn` method. -[Official docs](https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname) +[Official docs](https://jestjs.io/docs/jest-object#jestspyonobject-methodname) The more challenging part are mocks, which can be used for functions or even dependencies. ### Manual module mocks @@ -728,12 +739,12 @@ Manual mocks are used to mock modules across the entire Jest environment. This i unit testing by mocking out modules which cannot be easily consumed in our test environment. > **WARNING:** Do not use manual mocks if a mock should not be consistently applied in every spec (i.e. it's only needed by a few specs). -> Instead, consider using [`jest.mock(..)`](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options) +> Instead, consider using [`jest.mock(..)`](https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options) > (or a similar mocking function) in the relevant spec file. #### Where should I put manual mocks? -Jest supports [manual module mocks](https://jestjs.io/docs/en/manual-mocks) by placing a mock in a `__mocks__/` directory next to the source module +Jest supports [manual module mocks](https://jestjs.io/docs/manual-mocks) by placing a mock in a `__mocks__/` directory next to the source module (e.g. `app/assets/javascripts/ide/__mocks__`). **Don't do this.** We want to keep all of our test-related code in one place (the `spec/` folder). If a manual mock is needed for a `node_modules` package, use the `spec/frontend/__mocks__` folder. Here's an example of @@ -755,7 +766,7 @@ If a manual mock is needed for a CE module, place it in `spec/frontend/mocks/ce` any behavior, only provides a nice es6 compatible wrapper. - [`__mocks__/monaco-editor/index.js`](https://gitlab.com/gitlab-org/gitlab/blob/b7f914cddec9fc5971238cdf12766e79fa1629d7/spec/frontend/__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. + makes this package consumable by Jest. ### Keep mocks light @@ -766,7 +777,7 @@ Global mocks introduce magic and technically can reduce test coverage. When mock ### Additional mocking techniques -Consult the [official Jest docs](https://jestjs.io/docs/en/jest-object#mock-modules) for a full overview of the available mocking features. +Consult the [official Jest docs](https://jestjs.io/docs/jest-object#mock-modules) for a full overview of the available mocking features. ## Running Frontend Tests @@ -939,8 +950,8 @@ it('uses some HTML element', () => { Similar to [RSpec's parameterized tests](best_practices.md#table-based--parameterized-tests), Jest supports data-driven tests for: -- Individual tests using [`test.each`](https://jestjs.io/docs/en/api#testeachtable-name-fn-timeout) (aliased to `it.each`). -- Groups of tests using [`describe.each`](https://jestjs.io/docs/en/api#describeeachtable-name-fn-timeout). +- Individual tests using [`test.each`](https://jestjs.io/docs/api#testeachtable-name-fn-timeout) (aliased to `it.each`). +- Groups of tests using [`describe.each`](https://jestjs.io/docs/api#describeeachtable-name-fn-timeout). These can be useful for reducing repetition within tests. Each option can take an array of data values or a tagged template literal. @@ -1138,23 +1149,40 @@ These are very useful if you don't have a handle to the request's Promise, for e Both functions run `callback` on the next tick after the requests finish (using `setImmediate()`), to allow any `.then()` or `.catch()` handlers to run. -### `extendedWrapper` and `findByTestId` +### `shallowMountExtended` and `mountExtended` -Using `data-testid` is one of the [recommended ways to query DOM elements](#how-to-query-dom-elements). -You can use the `extendedWrapper` utility on the `wrapper` returned by `shalowMount`/`mount`. -By doing so, the `wrapper` provides you with the ability to perform a `findByTestId`, -which is a shortcut to the more verbose `wrapper.find('[data-testid="my-test-id"]');` +The `shallowMountExtended` and `mountExtended` utilities provide you with the ability to perform +any of the available [DOM Testing Library queries](https://testing-library.com/docs/queries/about) +by prefixing them with `find` or `findAll`. ```javascript -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; describe('FooComponent', () => { - const wrapper = extendedWrapper(shallowMount({ - template: `<div data-testid="my-test-id"></div>`, - })); + const wrapper = shallowMountExtended({ + template: ` + <div data-testid="gitlab-frontend-stack"> + <p>GitLab frontend stack</p> + <div role="tablist"> + <button role="tab" aria-selected="true">Vue.js</button> + <button role="tab" aria-selected="false">GraphQL</button> + <button role="tab" aria-selected="false">SCSS</button> + </div> + </div> + `, + }); + + it('finds elements with `findByTestId`', () => { + expect(wrapper.findByTestId('gitlab-frontend-stack').exists()).toBe(true); + }); + + it('finds elements with `findByText`', () => { + expect(wrapper.findByText('GitLab frontend stack').exists()).toBe(true); + expect(wrapper.findByText('TypeScript').exists()).toBe(false); + }); - it('exists', () => { - expect(wrapper.findByTestId('my-test-id').exists()).toBe(true); + it('finds elements with `findAllByRole`', () => { + expect(wrapper.findAllByRole('tab').length).toBe(3); }); }); ``` @@ -1189,7 +1217,7 @@ You can download any older version of Firefox from the releases FTP server, <htt ## Snapshots -By now you've probably heard of [Jest snapshot tests](https://jestjs.io/docs/en/snapshot-testing) and why they are useful for various reasons. +By now you've probably heard of [Jest snapshot tests](https://jestjs.io/docs/snapshot-testing) and why they are useful for various reasons. To use them within GitLab, there are a few guidelines that should be highlighted: - Treat snapshots as code @@ -1199,7 +1227,7 @@ To use them within GitLab, there are a few guidelines that should be highlighted Think of a snapshot test as a simple way to store a raw `String` representation of what you've put into the item being tested. This can be used to evaluate changes in a component, a store, a complex piece of generated output, etc. You can see more in the list below for some recommended `Do's and Don'ts`. While snapshot tests can be a very powerful tool. They are meant to supplement, not to replace unit tests. -Jest provides a great set of docs on [best practices](https://jestjs.io/docs/en/snapshot-testing#best-practices) that we should keep in mind when creating snapshots. +Jest provides a great set of docs on [best practices](https://jestjs.io/docs/snapshot-testing#best-practices) that we should keep in mind when creating snapshots. ### How does a snapshot work? @@ -1207,7 +1235,7 @@ A snapshot is purely a stringified version of what you ask to be tested on the l Should the outcome of your spec be different from what is in the generated snapshot file, you'll be notified about it by a failing test in your test suite. -Find all the details in Jests official documentation [https://jestjs.io/docs/en/snapshot-testing](https://jestjs.io/docs/en/snapshot-testing) +Find all the details in Jests official documentation [https://jestjs.io/docs/snapshot-testing](https://jestjs.io/docs/snapshot-testing) ### How to take a snapshot |