best_practices.md 53.9 KB
Newer Older
1 2 3 4 5 6 7 8
---
type: reference, dev
stage: none
group: Development
info: "See the Technical Writers assigned to Development Guidelines: https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments-to-development-guidelines"
description: "GitLab development guidelines - testing best practices."
---

9 10
# Testing best practices

11 12
## Test Design

13 14
Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests
as we do the design of our features.
15

16
When implementing a feature, we think about developing the right capabilities the right way. This helps us
17
narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing
18
the right tests, but then cover _all_ the important ways the test may fail. This can quickly widen our scope to
19 20
a level that is difficult to manage.

21
Test heuristics can help solve this problem. They concisely address many of the common ways bugs
22
manifest themselves in our code. When designing our tests, take time to review known test heuristics to inform
23
our test design. We can find some helpful heuristics documented in the Handbook in the
24
[Test Engineering](https://about.gitlab.com/handbook/engineering/quality/test-engineering/#test-heuristics) section.
25

26 27
## RSpec

28
To run RSpec tests:
29

30
```shell
31 32 33 34 35 36 37 38 39 40
# run test for a file
bin/rspec spec/models/project_spec.rb

# run test for the example on line 10 on that file
bin/rspec spec/models/project_spec.rb:10

# run tests matching the example name has that string
bin/rspec spec/models/project_spec.rb -e associations

# run all tests, will take hours for GitLab codebase!
41
bin/rspec
42 43 44

```

45
Use [Guard](https://github.com/guard/guard) to continuously monitor for changes and only run matching tests:
46

47
```shell
48 49 50
bundle exec guard
```

51
When using spring and guard together, use `SPRING=1 bundle exec guard` instead to make use of spring.
52

53 54 55 56 57 58 59 60 61 62 63 64 65 66
### Eager loading the application code

By default, the application code:

- Isn't eagerly loaded in the `test` environment.
- Is eagerly loaded in CI/CD (when `ENV['CI'].present?`) to surface any potential loading issues.

If you need to enable eager loading when executing tests,
use the `GITLAB_TEST_EAGER_LOAD` environment variable:

```shell
GITLAB_TEST_EAGER_LOAD=1 bin/rspec spec/models/project_spec.rb
```

67 68 69
If your test depends on all the application code that is being loaded, add the `:eager_load` tag.
This ensures that the application code is eagerly loaded before the test execution.

70 71 72 73
### Ruby warnings

> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47767) in GitLab 13.7.

Matthias Kaeppler's avatar
Matthias Kaeppler committed
74
We've enabled [deprecation warnings](https://ruby-doc.org/core-2.7.4/Warning.html)
75 76 77 78 79 80 81 82 83 84 85
by default when running specs. Making these warnings more visible to developers
helps upgrading to newer Ruby versions.

You can silence deprecation warnings by setting the environment variable
`SILENCE_DEPRECATIONS`, for example:

```shell
# silence all deprecation warnings
SILENCE_DEPRECATIONS=1 bin/rspec spec/models/project_spec.rb
```

86 87
### Test speed

88
GitLab has a massive test suite that, without [parallelization](../pipelines.md#test-suite-parallelization), can take hours
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
to run. It's important that we make an effort to write tests that are accurate
and effective _as well as_ fast.

Test performance is important to maintaining quality and velocity, and has a
direct impact on CI build times and thus fixed costs. We want thorough, correct,
and fast tests. Here you can find some information about tools and techniques
available to you to achieve that.

#### Don't request capabilities you don't need

We make it easy to add capabilities to our examples by annotating the example or
a parent context. Examples of these are:

- `:js` in feature specs, which runs a full JavaScript capable headless browser.
- `:clean_gitlab_redis_cache` which provides a clean Redis cache to the examples.
- `:request_store` which provides a request store to the examples.

106
We should reduce test dependencies, and avoiding
107 108 109
capabilities also reduces the amount of set-up needed.

`:js` is particularly important to avoid. This must only be used if the feature
110
test requires JavaScript reactivity in the browser. Using a headless
111 112 113 114 115 116
browser is much slower than parsing the HTML response from the app.

#### Optimize factory usage

A common cause of slow tests is excessive creation of objects, and thus
computation and DB time. Factories are essential to development, but they can
117
make inserting data into the DB so easy that we may be able to optimize.
118 119 120 121 122 123 124 125 126 127

The two basic techniques to bear in mind here are:

- **Reduce**: avoid creating objects, and avoid persisting them.
- **Reuse**: shared objects, especially nested ones we do not examine, can generally be shared.

To avoid creation, it is worth bearing in mind that:

- `instance_double` and `spy` are faster than `FactoryBot.build(...)`.
- `FactoryBot.build(...)` and `.build_stubbed` are faster than `.create`.
128 129
- Don't `create` an object when you can use `build`, `build_stubbed`, `attributes_for`,
  `spy`, or `instance_double`. Database persistence is slow!
130

131
Use [Factory Doctor](https://test-prof.evilmartians.io/#/profilers/factory_doctor) to find cases where database persistence is not needed in a given test.
132 133 134 135 136 137

```shell
# run test for path
FDOC=1 bin/rspec spec/[path]/[to]/[spec].rb
```

138
A common change is to use `build` or `build_stubbed` instead of `create`:
139 140 141 142 143 144 145 146 147

```ruby
# Old
let(:project) { create(:project) }

# New
let(:project) { build(:project) }
```

148
[Factory Profiler](https://test-prof.evilmartians.io/#/profilers/factory_prof) can help to identify repetitive database persistence via factories.
149 150 151 152 153 154 155 156 157

```shell
# run test for path
FPROF=1 bin/rspec spec/[path]/[to]/[spec].rb

# to visualize with a flamegraph
FPROF=flamegraph bin/rspec spec/[path]/[to]/[spec].rb
```

158
A common change is to use [`let_it_be`](#common-test-setup):
159 160

```ruby
161 162
# Old
let(:project) { create(:project) }
163

164 165
# New
let_it_be(:project) { create(:project) }
166 167
```

Igor Drozdov's avatar
Igor Drozdov committed
168 169 170
A common cause of a large number of created factories is [factory cascades](https://github.com/test-prof/test-prof/blob/master/docs/profilers/factory_prof.md#factory-flamegraph), which result when factories create and recreate associations.
They can be identified by a noticeable difference between `total time` and `top-level time` numbers:

171
```plaintext
Igor Drozdov's avatar
Igor Drozdov committed
172 173 174 175 176 177
   total   top-level     total time      time per call      top-level time               name

     208           0        9.5812s            0.0461s             0.0000s          namespace
     208          76       37.4214s            0.1799s            13.8749s            project
```

178 179 180 181 182
The table above shows us that we never create any `namespace` objects explicitly
(`top-level == 0`) - they are all created implicitly for us. But we still end up
with 208 of them (one for each project) and this takes 9.5 seconds.

In order to reuse a single object for all calls to a named factory in implicit parent associations,
Igor Drozdov's avatar
Igor Drozdov committed
183 184 185 186
[`FactoryDefault`](https://github.com/test-prof/test-prof/blob/master/docs/recipes/factory_default.md)
can be used:

```ruby
187
RSpec.describe API::Search, factory_default: :keep do
188
  let_it_be(:namespace) { create_default(:namespace) }
189 190
```

191
Then every project we create uses this `namespace`, without us having to pass
192
it as `namespace: namespace`. In order to make it work along with `let_it_be`, `factory_default: :keep`
193
must be explicitly specified. That keeps the default factory for every example in a suite instead of
194
recreating it for each example.
195

196 197 198
To prevent accidental reliance between test examples, objects created
with `create_default` are
[frozen](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/support/factory_default.rb).
199

200 201
Maybe we don't need to create 208 different projects - we
can create one and reuse it. In addition, we can see that only about 1/3 of the
202
projects we create are ones we ask for (76/208). There is benefit in setting
203 204 205
a default value for projects as well:

```ruby
206
  let_it_be(:project) { create_default(:project) }
Igor Drozdov's avatar
Igor Drozdov committed
207 208 209 210
```

In this case, the `total time` and `top-level time` numbers match more closely:

211 212 213
```plaintext
   total   top-level     total time      time per call      top-level time               name

Igor Drozdov's avatar
Igor Drozdov committed
214 215 216 217
      31          30        4.6378s            0.1496s             4.5366s            project
       8           8        0.0477s            0.0477s             0.0477s          namespace
```

218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
#### Identify slow tests

Running a spec with profiling is a good way to start optimizing a spec. This can
be done with:

```shell
bundle exec rspec --profile -- path/to/spec_file.rb
```

Which includes information like the following:

```plaintext
Top 10 slowest examples (10.69 seconds, 7.7% of total time):
  Issue behaves like an editable mentionable creates new cross-reference notes when the mentionable text is edited
    1.62 seconds ./spec/support/shared_examples/models/mentionable_shared_examples.rb:164
  Issue relative positioning behaves like a class that supports relative positioning .move_nulls_to_end manages to move nulls to the end, stacking if we cannot create enough space
    1.39 seconds ./spec/support/shared_examples/models/relative_positioning_shared_examples.rb:88
  Issue relative positioning behaves like a class that supports relative positioning .move_nulls_to_start manages to move nulls to the end, stacking if we cannot create enough space
    1.27 seconds ./spec/support/shared_examples/models/relative_positioning_shared_examples.rb:180
  Issue behaves like an editable mentionable behaves like a mentionable extracts references from its reference property
    0.99253 seconds ./spec/support/shared_examples/models/mentionable_shared_examples.rb:69
  Issue behaves like an editable mentionable behaves like a mentionable creates cross-reference notes
    0.94987 seconds ./spec/support/shared_examples/models/mentionable_shared_examples.rb:101
  Issue behaves like an editable mentionable behaves like a mentionable when there are cached markdown fields sends in cached markdown fields when appropriate
    0.94148 seconds ./spec/support/shared_examples/models/mentionable_shared_examples.rb:86
  Issue behaves like an editable mentionable when there are cached markdown fields when the markdown cache is stale persists the refreshed cache so that it does not have to be refreshed every time
    0.92833 seconds ./spec/support/shared_examples/models/mentionable_shared_examples.rb:153
  Issue behaves like an editable mentionable when there are cached markdown fields refreshes markdown cache if necessary
    0.88153 seconds ./spec/support/shared_examples/models/mentionable_shared_examples.rb:130
  Issue behaves like an editable mentionable behaves like a mentionable generates a descriptive back-reference
    0.86914 seconds ./spec/support/shared_examples/models/mentionable_shared_examples.rb:65
  Issue#related_issues returns only authorized related issues for given user
    0.84242 seconds ./spec/models/issue_spec.rb:335

Finished in 2 minutes 19 seconds (files took 1 minute 4.42 seconds to load)
277 examples, 0 failures, 1 pending
```

From this result, we can see the most expensive examples in our spec, giving us
257 258
a place to start. The most expensive examples here are in
shared examples; any reductions generally have a larger impact as
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
they are called in multiple places.

#### Avoid repeating expensive actions

While isolated examples are very clear, and help serve the purpose of specs as
specification, the following example shows how we can combine expensive
actions:

```ruby
subject { described_class.new(arg_0, arg_1) }

it 'creates an event' do
  expect { subject.execute }.to change(Event, :count).by(1)
end

it 'sets the frobulance' do
  expect { subject.execute }.to change { arg_0.reset.frobulance }.to('wibble')
end

it 'schedules a background job' do
279
  expect(BackgroundJob).to receive(:perform_async)
280

281
  subject.execute
282 283 284 285 286 287 288 289 290
end
```

If the call to `subject.execute` is expensive, then we are repeating the same
action just to make different assertions. We can reduce this repetition by
combining the examples:

```ruby
it 'performs the expected side-effects' do
291 292
  expect(BackgroundJob).to receive(:perform_async)

293 294 295 296 297 298 299 300 301 302 303 304
  expect { subject.execute }
    .to change(Event, :count).by(1)
    .and change { arg_0.frobulance }.to('wibble')
end
```

Be careful doing this, as this sacrifices clarity and test independence for
performance gains.

When combining tests, consider using `:aggregate_failures`, so that the full
results are available, and not just the first failure.

305 306
### General guidelines

307
- Use a single, top-level `RSpec.describe ClassName` block.
308 309 310
- Use `.method` to describe class methods and `#method` to describe instance
  methods.
- Use `context` to test branching logic.
311
- Try to match the ordering of tests to the ordering in the class.
312
- Try to follow the [Four-Phase Test](https://thoughtbot.com/blog/four-phase-test) pattern, using newlines
313 314 315
  to separate phases.
- Use `Gitlab.config.gitlab.host` rather than hard coding `'localhost'`
- Don't assert against the absolute value of a sequence-generated attribute (see
316
  [Gotchas](../gotchas.md#do-not-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
317 318
- Avoid using `expect_any_instance_of` or `allow_any_instance_of` (see
  [Gotchas](../gotchas.md#do-not-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
319
- Don't supply the `:each` argument to hooks because it's the default.
320
- On `before` and `after` hooks, prefer it scoped to `:context` over `:all`
321
- When using `evaluate_script("$('.js-foo').testSomething()")` (or `execute_script`) which acts on a given element,
322
  use a Capybara matcher beforehand (such as `find('.js-foo')`) to ensure the element actually exists.
323
- Use `focus: true` to isolate parts of the specs you want to run.
324
- Use [`:aggregate_failures`](https://relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures) when there is more than one expectation in a test.
325
- For [empty test description blocks](https://github.com/rubocop-hq/rspec-style-guide#it-and-specify), use `specify` rather than `it do` if the test is self-explanatory.
326 327 328 329
- Use `non_existing_record_id`/`non_existing_record_iid`/`non_existing_record_access_level`
  when you need an ID/IID/access level that doesn't actually exists. Using 123, 1234,
  or even 999 is brittle as these IDs could actually exist in the database in the
  context of a CI run.
330

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
### Coverage

[`simplecov`](https://github.com/colszowka/simplecov) is used to generate code test coverage reports.
These are generated automatically on the CI, but not when running tests locally. To generate partial reports
when you run a spec file on your machine, set the `SIMPLECOV` environment variable:

```shell
SIMPLECOV=1 bundle exec rspec spec/models/repository_spec.rb
```

Coverage reports are generated into the `coverage` folder in the app root, and you can open these in your browser, for example:

```shell
firefox coverage/index.html
```

Use the coverage reports to ensure your tests cover 100% of your code.

349 350
### System / Feature tests

351
NOTE:
352
Before writing a new system test, [please consider **not**
353 354 355 356 357 358 359 360 361 362 363 364 365 366
writing one](testing_levels.md#consider-not-writing-a-system-test)!

- Feature specs should be named `ROLE_ACTION_spec.rb`, such as
  `user_changes_password_spec.rb`.
- Use scenario titles that describe the success and failure paths.
- Avoid scenario titles that add no information, such as "successfully".
- Avoid scenario titles that repeat the feature title.
- Create only the necessary records in the database
- Test a happy path and a less happy path but that's it
- Every other possible path should be tested with Unit or Integration tests
- Test what's displayed on the page, not the internals of ActiveRecord models.
  For instance, if you want to verify that a record was created, add
  expectations that its attributes are displayed on the page, not that
  `Model.count` increased by one.
367
- It's ok to look for DOM elements, but don't abuse it, because it makes the tests
368 369
  more brittle

370
#### UI testing
371

372 373 374 375 376 377 378 379 380 381
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`.
382

383
If needed, you can scope interactions within a specific area of the page by using `within`.
384 385
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.
386

387 388 389 390 391 392 393 394
##### Externalized contents

Test expectations against externalized contents should call the same
externalizing method to match the translation. For example, you should use the `_`
method in Ruby and `__` method in JavaScript.

See [Internationalization for GitLab - Test files](../i18n/externalization.md#test-files) for details.

395 396 397 398 399 400
##### Actions

Where possible, use more specific [actions](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Actions), such as the ones below.

```ruby
# good
401
click_button _('Submit review')
402

403
click_link _('UI testing docs')
404

405
fill_in _('Search projects'), with: 'gitlab' # fill in text input with text
406

407
select _('Last updated'), from: 'Sort by' # select an option from a select input
408

409 410
check _('Checkbox label')
uncheck _('Checkbox label')
411

412
choose _('Radio input label')
413

414
attach_file(_('Attach a file'), '/path/to/file.png')
415 416 417 418 419 420 421 422

# 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')
423 424 425 426 427 428 429 430
```

##### Finders

Where possible, use more specific [finders](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Finders), such as the ones below.

```ruby
# good
431 432
find_button _('Submit review')
find_button _('Submit review'), disabled: true
433

434 435
find_link _('UI testing docs')
find_link _('UI testing docs'), href: docs_url
436

437 438 439 440 441
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
442 443 444

# acceptable when finding a element that is not a button, link, or field
find('[data-testid="element"]')
445 446 447 448
```

##### Matchers

449
Where possible, use more specific [matchers](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/RSpecMatchers), such as the ones below.
450 451 452

```ruby
# good
453 454 455
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
456

457 458
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
459

460 461 462
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
463

464 465
expect(page).to have_checked_field _('Checkbox label')
expect(page).to have_unchecked_field _('Radio input label')
466

467 468 469 470
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
471

472 473
expect(page).to have_text _('Some paragraph text.')
expect(page).to have_text _('Some paragraph text.'), exact: true # assert exact match
474 475 476

expect(page).to have_current_path 'gitlab/gitlab-test/-/issues'

477
expect(page).to have_title _('Not Found')
478

479
# acceptable when a more specific matcher above is not possible
480 481 482 483 484 485
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
```

Peter Hegman's avatar
Peter Hegman committed
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
##### Interacting with modals

Use the `within_modal` helper to interact with [GitLab UI modals](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/base-modal--default).

```ruby
include Spec::Support::Helpers::ModalHelpers

within_modal do
  expect(page).to have_link _('UI testing docs')

  fill_in _('Search projects'), with: 'gitlab'

  click_button 'Continue'
end
```

Furthermore, you can use `accept_gl_confirm` for confirmation modals that only need to be accepted.
This is helpful when migrating [`window.confirm()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) to [`confirmAction`](https://gitlab.com/gitlab-org/gitlab/-/blob/ee280ed2b763d1278ad38c6e7e8a0aff092f617a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js#L3).

```ruby
include Spec::Support::Helpers::ModalHelpers

accept_gl_confirm do
  click_button 'Delete user'
end
```

You can also pass the expected confirmation message and button text to `accept_gl_confirm`.

```ruby
include Spec::Support::Helpers::ModalHelpers

accept_gl_confirm('Are you sure you want to delete this user?', button_text: 'Delete') do
  click_button 'Delete user'
end
```

523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
##### 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
541
```
542

543 544
You can also find a number of GitLab custom helpers in the `spec/support/helpers/` directory.

545 546
#### Live debug

547 548
Sometimes you may need to debug Capybara tests by observing browser behavior.

549
You can pause Capybara and view the website on the browser by using the
550
`live_debug` method in your spec. The current page is automatically opened
551
in your default browser.
552
You may need to sign in first (the current user's credentials are displayed in
553 554
the terminal).

555
To resume the test run, press any key.
556 557 558

For example:

559
```shell
560 561 562 563 564 565
$ bin/rspec spec/features/auto_deploy_spec.rb:34
Running via Spring preloader in process 8999
Run options: include {:locations=>{"./spec/features/auto_deploy_spec.rb"=>[34]}}

Current example is paused for live debugging
The current user credentials are: user2 / 12345678
566
Press any key to resume the execution of the example!
567 568 569 570 571 572 573
Back to the example!
.

Finished in 34.51 seconds (files took 0.76702 seconds to load)
1 example, 0 failures
```

574
`live_debug` only works on JavaScript enabled specs.
575

576 577
#### Run `:js` spec in a visible browser

578
Run the spec with `WEBDRIVER_HEADLESS=0`, like this:
579

580
```shell
581
WEBDRIVER_HEADLESS=0 bin/rspec some_spec.rb
582 583
```

584
The test completes quickly, but this gives you an idea of what's happening.
585
Using `live_debug` with `WEBDRIVER_HEADLESS=0` pauses the open browser, and does not
586
open the page again. This can be used to debug and inspect elements.
587

588
You can also add `byebug` or `binding.pry` to pause execution and [step through](../pry_debugging.md#stepping)
589 590 591 592 593 594 595 596 597
the test.

#### Screenshots

We use the `capybara-screenshot` gem to automatically take a screenshot on
failure. In CI you can download these files as job artifacts.

Also, you can manually take screenshots at any point in a test by adding the
methods below. Be sure to remove them when they are no longer needed! See
598
<https://github.com/mattheworiordan/capybara-screenshot#manual-screenshots> for
599 600 601 602 603 604 605 606
more.

Add `screenshot_and_save_page` in a `:js` spec to screenshot what Capybara
"sees", and save the page source.

Add `screenshot_and_open_image` in a `:js` spec to screenshot what Capybara
"sees", and automatically open the image.

607 608
The HTML dumps created by this are missing CSS.
This results in them looking very different from the actual application.
609
There is a [small hack](https://gitlab.com/gitlab-org/gitlab-foss/snippets/1718469) to add CSS which makes debugging easier.
610

611 612
### Fast unit tests

613
Some classes are well-isolated from Rails. You should be able to test them
614 615 616
without the overhead added by the Rails environment and Bundler's `:default`
group's gem loading. In these cases, you can `require 'fast_spec_helper'`
instead of `require 'spec_helper'` in your test file, and your test should run
617
really fast because:
618

619
- Gem loading is skipped
620
- Rails app boot is skipped
621
- GitLab Shell and Gitaly setup are skipped
622 623
- Test repositories setup are skipped

624
`fast_spec_helper` also support autoloading classes that are located inside the
625 626
`lib/` directory. If your class or module is using only
code from the `lib/` directory, you don't need to explicitly load any
627 628 629 630
dependencies. `fast_spec_helper` also loads all ActiveSupport extensions,
including core extensions that are commonly used in the Rails environment.

Note that in some cases, you might still have to load some dependencies using
631 632
`require_dependency` when a code is using gems or a dependency is not located
in `lib/`.
633 634 635

For example, if you want to test your code that is calling the
`Gitlab::UntrustedRegexp` class, which under the hood uses `re2` library, you
636 637 638 639 640
should either:

- Add `require_dependency 're2'` to files in your library that need `re2` gem,
  to make this requirement explicit. This approach is preferred.
- Add it to the spec itself.
641

642 643
It takes around one second to load tests that are using `fast_spec_helper`
instead of 30+ seconds in case of a regular `spec_helper`.
644

Thong Kuah's avatar
Thong Kuah committed
645
### `subject` and `let` variables
646

647
The GitLab RSpec suite has made extensive use of `let`(along with its strict, non-lazy
648
version `let!`) variables to reduce duplication. However, this sometimes [comes at the cost of clarity](https://thoughtbot.com/blog/lets-not),
649 650
so we need to set some guidelines for their use going forward:

651
- `let!` variables are preferable to instance variables. `let` variables
652
  are preferable to `let!` variables. Local variables are preferable to
653
  `let` variables.
654 655 656 657 658 659 660 661 662
- Use `let` to reduce duplication throughout an entire spec file.
- Don't use `let` to define variables used by a single test; define them as
  local variables inside the test's `it` block.
- Don't define a `let` variable inside the top-level `describe` block that's
  only used in a more deeply-nested `context` or `describe` block. Keep the
  definition as close as possible to where it's used.
- Try to avoid overriding the definition of one `let` variable with another.
- Don't define a `let` variable that's only used by the definition of another.
  Use a helper method instead.
663
- `let!` variables should be used only in case if strict evaluation with defined
664
  order is required, otherwise `let` suffices. Remember that `let` is lazy and won't
665
  be evaluated until it is referenced.
666 667 668
- Avoid referencing `subject` in examples. Use a named subject `subject(:name)`, or a `let` variable instead, so
  the variable has a contextual name.
- If the `subject` is never referenced inside examples, then it's acceptable to define the `subject` without a name.
669

670 671 672 673
### Common test setup

In some cases, there is no need to recreate the same object for tests
again for each example. For example, a project and a guest of that project
674
are needed to test issues on the same project, so one project and user are enough for the entire file.
675 676 677 678 679

As much as possible, do not implement this using `before(:all)` or `before(:context)`. If you do,
you would need to manually clean up the data as those hooks run outside a database transaction.

Instead, this can be achieved by using
680 681
[`let_it_be`](https://test-prof.evilmartians.io/#/recipes/let_it_be) variables and the
[`before_all`](https://test-prof.evilmartians.io/#/recipes/before_all) hook
682 683
from the [`test-prof` gem](https://rubygems.org/gems/test-prof).

684
```ruby
685 686 687 688 689 690 691 692
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }

before_all do
  project.add_guest(user)
end
```

693
This results in only one `Project`, `User`, and `ProjectMember` created for this context.
694

695
`let_it_be` and `before_all` are also available in nested contexts. Cleanup after the context
696 697
is handled automatically using a transaction rollback.

698
Note that if you modify an object defined inside a `let_it_be` block,
699 700 701 702 703
then you must do one of the following:

- Reload the object as needed.
- Use the `let_it_be_with_reload` alias.
- Specify the `reload` option to reload for every example.
704

705
```ruby
706
let_it_be_with_reload(:project) { create(:project) }
707 708 709
let_it_be(:project, reload: true) { create(:project) }
```

710 711
You can also use the `let_it_be_with_refind` alias, or specify the `refind`
option as well to completely load a new object.
712

713
```ruby
714
let_it_be_with_refind(:project) { create(:project) }
715 716 717
let_it_be(:project, refind: true) { create(:project) }
```

718 719 720 721 722 723
Note that `let_it_be` cannot be used with factories that has stubs, such as `allow`.
The reason is that `let_it_be` happens in a `before(:all)` block, and RSpec does not
allow stubs in `before(:all)`.
See this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/340487) for more details.
To resolve, use `let`, or change the factory to not use stubs.

724 725
### Time-sensitive tests

726 727 728
[`ActiveSupport::Testing::TimeHelpers`](https://api.rubyonrails.org/v6.0.3.1/classes/ActiveSupport/Testing/TimeHelpers.html)
can be used to verify things that are time-sensitive. Any test that exercises or verifies something time-sensitive
should make use of these helpers to prevent transient test failures.
729 730 731 732 733 734 735

Example:

```ruby
it 'is overdue' do
  issue = build(:issue, due_date: Date.tomorrow)

736
  travel_to(3.days.from_now) do
737 738 739 740 741
    expect(issue).to be_overdue
  end
end
```

742 743
### Feature flags in tests

744
This section was moved to [developing with feature flags](../feature_flags/index.md).
745

746 747 748 749
### Pristine test environments

The code exercised by a single GitLab test may access and modify many items of
data. Without careful preparation before a test runs, and cleanup afterward,
750
a test can change data in a way that affects the behavior of
751 752 753 754
following tests. This should be avoided at all costs! Fortunately, the existing
test framework handles most cases already.

When the test environment does get polluted, a common outcome is
755 756 757
[flaky tests](flaky_tests.md). Pollution often manifests as an order
dependency: running spec A followed by spec B reliably fails, but running
spec B followed by spec A reliably succeeds. In these cases, you can use
758 759 760 761 762 763 764 765
`rspec --bisect` (or a manual pairwise bisect of spec files) to determine which
spec is at fault. Fixing the problem requires some understanding of how the test
suite ensures the environment is pristine. Read on to discover more about each
data store!

#### SQL database

This is managed for us by the `database_cleaner` gem. Each spec is surrounded in
766 767
a transaction, which is rolled back after the test completes. Certain specs
instead issue `DELETE FROM` queries against every table after completion. This
768 769 770 771 772 773
allows the created rows to be viewed from multiple database connections, which
is important for specs that run in a browser, or migration specs, among others.

One consequence of using these strategies, instead of the well-known
`TRUNCATE TABLES` approach, is that primary keys and other sequences are **not**
reset across specs. So if you create a project in spec A, then create a project
774
in spec B, the first has `id=1`, while the second has `id=2`.
775 776 777 778 779 780 781 782

This means that specs should **never** rely on the value of an ID, or any other
sequence-generated column. To avoid accidental conflicts, specs should also
avoid manually specifying any values in these kinds of columns. Instead, leave
them unspecified, and look up the value after the row is created.

#### Redis

783
GitLab stores two main categories of data in Redis: cached items, and Sidekiq
784 785 786 787 788 789 790 791
jobs.

In most specs, the Rails cache is actually an in-memory store. This is replaced
between specs, so calls to `Rails.cache.read` and `Rails.cache.write` are safe.
However, if a spec makes direct Redis calls, it should mark itself with the
`:clean_gitlab_redis_cache`, `:clean_gitlab_redis_shared_state` or
`:clean_gitlab_redis_queues` traits as appropriate.

792 793 794
#### Background jobs / Sidekiq

By default, Sidekiq jobs are enqueued into a jobs array and aren't processed.
795
If a test queues Sidekiq jobs and need them to be processed, the
796
`:sidekiq_inline` trait can be used.
797 798

The `:sidekiq_might_not_need_inline` trait was added when [Sidekiq inline mode was
799
changed to fake mode](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/15479)
800
to all the tests that needed Sidekiq to actually process jobs. Tests with
801
this trait should be either fixed to not rely on Sidekiq processing jobs, or their
802 803 804
`:sidekiq_might_not_need_inline` trait should be updated to `:sidekiq_inline` if
the processing of background jobs is needed/expected.

805 806 807
The usage of `perform_enqueued_jobs` is useful only for testing delayed mail
deliveries, because our Sidekiq workers aren't inheriting from `ApplicationJob`
/ `ActiveJob::Base`.
808

809 810 811 812 813 814
#### DNS

DNS requests are stubbed universally in the test suite
(as of [!22368](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22368)), as DNS can
cause issues depending on the developer's local network. There are RSpec labels
available in `spec/support/dns.rb` which you can apply to tests if you need to
815
bypass the DNS stubbing, like this:
816

817
```ruby
818 819 820 821 822 823
it "really connects to Prometheus", :permit_dns do
```

And if you need more specific control, the DNS blocking is implemented in
`spec/support/helpers/dns_helpers.rb` and these methods can be called elsewhere.

824 825 826 827 828 829
#### Stubbing File methods

In the situations where you need to
[stub](https://relishapp.com/rspec/rspec-mocks/v/3-9/docs/basics/allowing-messages)
methods such as `File.read`, make sure to:

830 831
1. Stub `File.read` for only the file path you are interested in.
1. Call the original implementation for other file paths.
832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849

Otherwise `File.read` calls from other parts of the codebase get
stubbed incorrectly. You should use the `stub_file_read`, and
`expect_file_read` helper methods which does the stubbing for
`File.read` correctly.

```ruby
# bad, all Files will read and return nothing
allow(File).to receive(:read)

# good
stub_file_read(my_filepath)

# also OK
allow(File).to receive(:read).and_call_original
allow(File).to receive(:read).with(my_filepath)
```

850
#### File system
851

852
File system data can be roughly split into "repositories", and "everything else".
853 854
Repositories are stored in `tmp/tests/repositories`. This directory is emptied
before a test run starts, and after the test run ends. It is not emptied between
855
specs, so created repositories accumulate in this directory over the
856 857 858 859 860
lifetime of the process. Deleting them is expensive, but this could lead to
pollution unless carefully managed.

To avoid this, [hashed storage](../../administration/repository_storage_types.md)
is enabled in the test suite. This means that repositories are given a unique
861 862
path that depends on their project's ID. Because the project IDs are not reset
between specs, each spec gets its own repository on disk,
863 864 865 866 867 868 869 870 871 872 873 874 875
and prevents changes from being visible between specs.

If a spec manually specifies a project ID, or inspects the state of the
`tmp/tests/repositories/` directory directly, then it should clean up the
directory both before and after it runs. In general, these patterns should be
completely avoided.

Other classes of file linked to database objects, such as uploads, are generally
managed in the same way. With hashed storage enabled in the specs, they are
written to disk in locations determined by ID, so conflicts should not occur.

Some specs disable hashed storage by passing the `:legacy_storage` trait to the
`projects` factory. Specs that do this must **never** override the `path` of the
876 877 878
project, or any of its groups. The default path includes the project ID, so it
does not conflict. If two specs create a `:legacy_storage` project with the same
path, they use the same repository on disk and lead to test environment
879 880 881
pollution.

Other files must be managed manually by the spec. If you run code that creates a
882
`tmp/test-file.csv` file, for instance, the spec must ensure that the file is
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
removed as part of cleanup.

#### Persistent in-memory application state

All the specs in a given `rspec` run share the same Ruby process, which means
they can affect each other by modifying Ruby objects that are accessible between
specs. In practice, this means global variables, and constants (which includes
Ruby classes, modules, etc).

Global variables should generally not be modified. If absolutely necessary, a
block like this can be used to ensure the change is rolled back afterwards:

```ruby
around(:each) do |example|
  old_value = $0

  begin
    $0 = "new-value"
    example.run
  ensure
    $0 = old_value
  end
end
```

If a spec needs to modify a constant, it should use the `stub_const` helper to
ensure the change is rolled back.

If you need to modify the contents of the `ENV` constant, you can use the
`stub_env` helper method instead.

While most Ruby **instances** are not shared between specs, **classes**
and **modules** generally are. Class and module instance variables, accessors,
class variables, and other stateful idioms, should be treated in the same way as
917
global variables. Don't modify them unless you have to! In particular, prefer
918
using expectations, or dependency injection along with stubs, to avoid the need
919 920
for modifications. If you have no other choice, an `around` block like the global
variables example can be used, but avoid this if at all possible.
921

922 923 924 925 926 927 928 929 930 931 932 933 934
#### Elasticsearch specs

> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61171) in GitLab 14.0.

Specs that require Elasticsearch must be marked with the `:elastic` trait. This
creates and deletes indices between examples to ensure a clean index, so that there is no room
for polluting the tests with nonessential data.
Most tests for Elasticsearch logic relate to:

- Creating data in Postgres and waiting for it to be indexed in Elasticsearch.
- Searching for that data.
- Ensuring that the test gives the expected result.

935
There are some exceptions, such as checking for structural changes rather than individual records in an index.
936 937

The `:elastic_with_delete_by_query` trait was added to reduce run time for pipelines by creating and deleting indices
938
at the start and end of each context only. The [Elasticsearch DeleteByQuery API](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html)
939 940 941
is used to delete data in all indices in between examples to ensure a clean index.

Note that Elasticsearch indexing uses [`Gitlab::Redis::SharedState`](../../../ee/development/redis.md#gitlabrediscachesharedstatequeues).
942 943
Therefore, the Elasticsearch traits dynamically use the `:clean_gitlab_redis_shared_state` trait.
You do NOT need to add `:clean_gitlab_redis_shared_state` manually.
944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962

Specs using Elasticsearch require that you:

- Create data in Postgres and then index it into Elasticsearch.
- Enable Application Settings for Elasticsearch (which is disabled by default).

To do so, use:

```ruby
before do
  stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
end
```

Additionally, you can use the `ensure_elasticsearch_index!` method to overcome the asynchronous nature of Elasticsearch.
It uses the [Elasticsearch Refresh API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html#refresh-api-desc)
to make sure all operations performed on an index since the last refresh are available for search. This method is typically
called after loading data into Postgres to ensure the data is indexed and searchable.

963 964
#### Test Snowplow events

965
WARNING:
966
Snowplow performs **runtime type checks** by using the [contracts gem](https://rubygems.org/gems/contracts).
967
Because Snowplow is **by default disabled in tests and development**, it can be hard to
968 969
**catch exceptions** when mocking `Gitlab::Tracking`.

970 971
To catch runtime errors due to type checks, you can enable Snowplow in tests. Mark the spec with
`:snowplow` and use the `expect_snowplow_event` helper, which checks for
972 973 974 975 976 977 978 979 980 981
calls to `Gitlab::Tracking#event`.

```ruby
describe '#show', :snowplow do
  it 'tracks snowplow events' do
    get :show

    expect_snowplow_event(
      category: 'Experiment',
      action: 'start',
982 983
      namespace: group,
      project: project
984 985 986 987 988
    )
    expect_snowplow_event(
      category: 'Experiment',
      action: 'sent',
      property: 'property',
989
      label: 'label',
990 991
      namespace: group,
      project: project
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
    )
  end
end
```

When you want to ensure that no event got called, you can use `expect_no_snowplow_event`.

```ruby
  describe '#show', :snowplow do
    it 'does not track any snowplow events' do
      get :show

      expect_no_snowplow_event
    end
  end
```

1009
#### Test Snowplow context against the schema
1010

1011 1012 1013
The [Snowplow schema matcher](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60480)
helps to reduce validation errors by testing Snowplow context against the JSON schema.
The schema matcher accepts the following parameters:
1014

1015 1016
- `schema path`
- `context`
1017

1018
To add a schema matcher spec:
1019

1020 1021 1022 1023 1024 1025 1026 1027 1028
1. Add a new schema to the [Iglu repository](https://gitlab.com/gitlab-org/iglu),
   then copy the same schema to the `spec/fixtures/product_intelligence/` directory.
1. In the copied schema, remove the `"$schema"` key and value. We do not need it for specs
   and the spec fails if we keep the key, as it tries to look for the schema in the URL.
1. Use the following snippet to call the schema matcher:

   ```ruby
   match_snowplow_context_schema(schema_path: '<filename from step 1>', context: <Context Hash> )
   ```
1029

1030 1031 1032 1033 1034 1035 1036
### Table-based / Parameterized tests

This style of testing is used to exercise one piece of code with a comprehensive
range of inputs. By specifying the test case once, alongside a table of inputs
and the expected output for each, your tests can be made easier to read and more
compact.

1037
We use the [RSpec::Parameterized](https://github.com/tomykaira/rspec-parameterized)
1038 1039 1040 1041 1042 1043 1044
gem. A short example, using the table syntax and checking Ruby equality for a
range of inputs, might look like this:

```ruby
describe "#==" do
  using RSpec::Parameterized::TableSyntax

1045 1046 1047
  let(:one) { 1 }
  let(:two) { 2 }

1048
  where(:a, :b, :result) do
1049 1050 1051 1052 1053 1054
    1         | 1         | true
    1         | 2         | false
    true      | true      | true
    true      | false     | false
    ref(:one) | ref(:one) | true  # let variables must be referenced using `ref`
    ref(:one) | ref(:two) | false
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
  end

  with_them do
    it { expect(a == b).to eq(result) }

    it 'is isomorphic' do
      expect(b == a).to eq(result)
    end
  end
end
```

1067 1068
<!-- vale gitlab.Spelling = NO -->

1069
WARNING:
1070
Only use simple values as input in the `where` block. Using procs, stateful
1071
objects, FactoryBot-created objects, and similar items can lead to
1072
[unexpected results](https://github.com/tomykaira/rspec-parameterized/issues/8).
1073

1074
<!-- vale gitlab.Spelling = YES -->
1075

1076 1077 1078
### Prometheus tests

Prometheus metrics may be preserved from one test run to another. To ensure that metrics are
1079
reset before each example, add the `:prometheus` tag to the RSpec test.
1080

1081 1082 1083
### Matchers

Custom matchers should be created to clarify the intent and/or hide the
1084
complexity of RSpec expectations. They should be placed under
1085
`spec/support/matchers/`. Matchers can be placed in subfolder if they apply to
1086
a certain type of specs only (such as features or requests) but shouldn't be if
1087 1088
they apply to multiple type of specs.

1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
#### `be_like_time`

Time returned from a database can differ in precision from time objects
in Ruby, so we need flexible tolerances when comparing in specs. We can
use `be_like_time` to compare that times are within one second of each
other.

Example:

```ruby
expect(metrics.merged_at).to be_like_time(time)
```

1102 1103
#### `have_gitlab_http_status`

1104 1105
Prefer `have_gitlab_http_status` over `have_http_status` and
`expect(response.status).to` because the former
1106 1107 1108 1109 1110 1111
could also show the response body whenever the status mismatched. This would
be very useful whenever some tests start breaking and we would love to know
why without editing the source and rerun the tests.

This is especially useful whenever it's showing 500 internal server error.

1112 1113 1114 1115 1116 1117 1118 1119 1120
Prefer named HTTP status like `:no_content` over its numeric representation
`206`. See a list of [supported status codes](https://github.com/rack/rack/blob/f2d2df4016a906beec755b63b4edfcc07b58ee05/lib/rack/utils.rb#L490).

Example:

```ruby
expect(response).to have_gitlab_http_status(:ok)
```

1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
#### `match_schema` and `match_response_schema`

The `match_schema` matcher allows validating that the subject matches a
[JSON schema](https://json-schema.org/). The item inside `expect` can be
a JSON string or a JSON-compatible data structure.

`match_response_schema` is a convenience matcher for using with a
response object. from a [request
spec](testing_levels.md#integration-tests).

Examples:

```ruby
# Matches against spec/fixtures/api/schemas/prometheus/additional_metrics_query_result.json
expect(data).to match_schema('prometheus/additional_metrics_query_result')

# Matches against ee/spec/fixtures/api/schemas/board.json
expect(data).to match_schema('board', dir: 'ee')

# Matches against a schema made up of Ruby data structures
expect(data).to match_schema(Atlassian::Schemata.build_info)
```

#### `be_valid_json`

`be_valid_json` allows validating that a string parses as JSON and gives
a non-empty result. To combine it with the schema matching above, use
`and`:

```ruby
expect(json_string).to be_valid_json

expect(json_string).to be_valid_json.and match_schema(schema)
```

1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
#### `be_one_of(collection)`

The inverse of `include`, tests that the `collection` includes the expected
value:

```ruby
expect(:a).to be_one_of(%i[a b c])
expect(:z).not_to be_one_of(%i[a b c])
```

1166 1167 1168 1169
### Testing query performance

Testing query performance allows us to:

1170 1171
- Assert that N+1 problems do not exist in a block of code.
- Ensure that the number of queries in a block of code does not increase unnoticed.
1172 1173 1174 1175

#### QueryRecorder

`QueryRecorder` allows profiling and testing of the number of database queries
1176
performed in a given block of code.
1177 1178 1179 1180 1181 1182 1183 1184 1185 1186

See the [`QueryRecorder`](../query_recorder.md) section for more details.

#### GitalyClient

`Gitlab::GitalyClient.get_request_count` allows tests of the number of Gitaly queries
made by a given block of code:

See the [`Gitaly Request Counts`](../gitaly.md#request-counts) section for more details.

1187 1188
### Shared contexts

1189 1190 1191 1192 1193
Shared contexts only used in one spec file can be declared inline.
Any shared contexts used by more than one spec file:

- Should be placed under `spec/support/shared_contexts/`.
- Can be placed in subfolder if they apply to a certain type of specs only
1194
  (such as features or requests) but shouldn't be if they apply to multiple type of specs.
1195

1196
Each file should include only one context and have a descriptive name, such as
1197 1198 1199 1200
`spec/support/shared_contexts/controllers/githubish_import_controller_shared_context.rb`.

### Shared examples

1201 1202 1203 1204 1205
Shared examples only used in one spec file can be declared inline.
Any shared examples used by more than one spec file:

- Should be placed under `spec/support/shared_examples/`.
- Can be placed in subfolder if they apply to a certain type of specs only
1206
  (such as features or requests) but shouldn't be if they apply to multiple type of specs.
1207

1208
Each file should include only one context and have a descriptive name, such as
1209 1210 1211 1212 1213 1214
`spec/support/shared_examples/controllers/githubish_import_controller_shared_example.rb`.

### Helpers

Helpers are usually modules that provide some methods to hide the complexity of
specific RSpec examples. You can define helpers in RSpec files if they're not
1215
intended to be shared with other specs. Otherwise, they should be placed
1216 1217
under `spec/support/helpers/`. Helpers can be placed in a subfolder if they apply
to a certain type of specs only (such as features or requests) but shouldn't be
1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228
if they apply to multiple type of specs.

Helpers should follow the Rails naming / namespacing convention. For instance
`spec/support/helpers/cycle_analytics_helpers.rb` should define:

```ruby
module Spec
  module Support
    module Helpers
      module CycleAnalyticsHelpers
        def create_commit_referencing_issue(issue, branch_name: random_git_name)
1229
          project.repository.add_branch(user, branch_name, 'main')
1230 1231 1232 1233 1234 1235 1236 1237
          create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
        end
      end
    end
  end
end
```

1238
Helpers should not change the RSpec configuration. For instance, the helpers module
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248
described above should not include:

```ruby
RSpec.configure do |config|
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers
end
```

### Factories

1249
GitLab uses [factory_bot](https://github.com/thoughtbot/factory_bot) as a test fixture replacement.
1250 1251 1252 1253

- Factory definitions live in `spec/factories/`, named using the pluralization
  of their corresponding model (`User` factories are defined in `users.rb`).
- There should be only one top-level factory definition per file.
1254 1255
- FactoryBot methods are mixed in to all RSpec groups. This means you can (and
  should) call `create(...)` instead of `FactoryBot.create(...)`.
1256
- Make use of [traits](https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#traits) to clean up definitions and usages.
1257 1258 1259 1260
- When defining a factory, don't define attributes that are not required for the
  resulting record to pass validation.
- When instantiating from a factory, don't supply attributes that aren't
  required by the test.
1261 1262 1263 1264
- Prefer [implicit](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#implicit-definition)
  or [explicit](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#explicit-definition)
  association definitions instead of using `create` / `build` for association setup.
  See [issue #262624](https://gitlab.com/gitlab-org/gitlab/-/issues/262624) for further context.
1265
- Factories don't have to be limited to `ActiveRecord` objects.
1266
  [See example](https://gitlab.com/gitlab-org/gitlab-foss/commit/0b8cefd3b2385a21cfed779bd659978c0402766d).
1267
- Factories and their traits should produce valid objects that are [verified by specs](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/factories_spec.rb).
1268 1269
- Avoid the use of [`skip_callback`](https://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html#method-i-skip_callback) in factories.
  See [issue #247865](https://gitlab.com/gitlab-org/gitlab/-/issues/247865) for details.
1270 1271 1272

### Fixtures

1273
All fixtures should be placed under `spec/fixtures/`.
1274

1275 1276
### Repositories

1277
Testing some functionality, such as merging a merge request, requires a Git
1278
repository with a certain state to be present in the test environment. GitLab
1279
maintains the [`gitlab-test`](https://gitlab.com/gitlab-org/gitlab-test)
1280 1281 1282 1283 1284 1285 1286 1287
repository for certain common cases - you can ensure a copy of the repository is
used with the `:repository` trait for project factories:

```ruby
let(:project) { create(:project, :repository) }
```

Where you can, consider using the `:custom_repo` trait instead of `:repository`.
1288
This allows you to specify exactly what files appear in the `main` branch
1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302
of the project's repository. For example:

```ruby
let(:project) do
  create(
    :project, :custom_repo,
    files: {
      'README.md'       => 'Content here',
      'foo/bar/baz.txt' => 'More content here'
    }
  )
end
```

1303
This creates a repository containing two files, with default permissions and
1304 1305
the specified content.

1306
### Configuration
1307

1308
RSpec configuration files are files that change the RSpec configuration (like
1309
`RSpec.configure do |config|` blocks). They should be placed under
1310
`spec/support/`.
1311

1312 1313
Each file should be related to a specific domain, such as
`spec/support/capybara.rb` or `spec/support/carrierwave.rb`.
1314

1315 1316
If a helpers module applies only to a certain kind of specs, it should add
modifiers to the `config.include` call. For instance if
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326
`spec/support/helpers/cycle_analytics_helpers.rb` applies to `:lib` and
`type: :model` specs only, you would write the following:

```ruby
RSpec.configure do |config|
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers, :lib
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers, type: :model
end
```

1327
If a configuration file only consists of `config.include`, you can add these
1328 1329 1330 1331 1332 1333 1334
`config.include` directly in `spec/spec_helper.rb`.

For very generic helpers, consider including them in the `spec/support/rspec.rb`
file which is used by the `spec/fast_spec_helper.rb` file. See
[Fast unit tests](#fast-unit-tests) for more details about the
`spec/fast_spec_helper.rb` file.

1335 1336 1337 1338
### Test environment logging

Services for the test environment are automatically configured and started when
tests are run, including Gitaly, Workhorse, Elasticsearch, and Capybara. When run in CI, or
1339
if the service needs to be installed, the test environment logs information
1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368
about set-up time, producing log messages like the following:

```plaintext
==> Setting up Gitaly...
    Gitaly set up in 31.459649 seconds...

==> Setting up GitLab Workhorse...
    GitLab Workhorse set up in 29.695619 seconds...
fatal: update refs/heads/diff-files-symlink-to-image: invalid <newvalue>: 8cfca84
From https://gitlab.com/gitlab-org/gitlab-test
 * [new branch]      diff-files-image-to-symlink -> origin/diff-files-image-to-symlink
 * [new branch]      diff-files-symlink-to-image -> origin/diff-files-symlink-to-image
 * [new branch]      diff-files-symlink-to-text -> origin/diff-files-symlink-to-text
 * [new branch]      diff-files-text-to-symlink -> origin/diff-files-text-to-symlink
   b80faa8..40232f7  snippet/multiple-files -> origin/snippet/multiple-files
 * [new branch]      testing/branch-with-#-hash -> origin/testing/branch-with-#-hash

==> Setting up GitLab Elasticsearch Indexer...
    GitLab Elasticsearch Indexer set up in 26.514623 seconds...
```

This information is omitted when running locally and when no action needs
to be performed. If you would always like to see these messages, set the
following environment variable:

```shell
GITLAB_TESTING_LOG_LEVEL=debug
```

1369 1370 1371
---

[Return to Testing documentation](index.md)