Testing Standards and Style Guidelines
This guide outlines standards and best practices for automated testing of GitLab CE and EE.
It is meant to be an extension of the thoughtbot testing styleguide. If this guide defines a rule that contradicts the thoughtbot guide, this guide takes precedence. Some guidelines may be repeated verbatim to stress their importance.
Factories
GitLab uses factory_girl as a test fixture replacement.
- Factory definitions live in
spec/factories/, named using the pluralization of their corresponding model (Userfactories are defined inusers.rb). - There should be only one top-level factory definition per file.
- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and
should) call
create(...)instead ofFactoryGirl.create(...). - Make use of traits to clean up definitions and usages.
- 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.
- Factories don't have to be limited to
ActiveRecordobjects. See example.
JavaScript
GitLab uses Karma to run its Jasmine JavaScript specs. They can be run on
the command line via bundle exec karma.
- JavaScript tests live in
spec/javascripts/, matching the folder structure ofapp/assets/javascripts/:app/assets/javascripts/behaviors/autosize.jshas a correspondingspec/javascripts/behaviors/autosize_spec.jsfile. -
Haml fixtures required for JavaScript tests live in
spec/javascripts/fixtures. They should contain the bare minimum amount of markup necessary for the test.Warning: Keep in mind that a Rails view may change and invalidate your test, but everything will still pass because your fixture doesn't reflect the latest view. Because of this we encourage you to generate fixtures from actual rails views whenever possible.
Keep in mind that in a CI environment, these tests are run in a headless browser and you will not have access to certain APIs, such as
Notification, which will have to be stubbed.
For more information, see the frontend testing guide.
RSpec
General Guidelines
- Use a single, top-level
describe ClassNameblock. - Use
described_classinstead of repeating the class name being described. - Use
.methodto describe class methods and#methodto describe instance methods. - Use
contextto test branching logic. - Use multi-line
do...endblocks forbeforeandafter, even when it would fit on a single line. - Don't
describesymbols (see Gotchas). - Don't assert against the absolute value of a sequence-generated attribute (see Gotchas).
- Don't supply the
:eachargument to hooks since it's the default. - Prefer
not_tototo_not(this is enforced by Rubocop). - Try to match the ordering of tests to the ordering within the class.
- Try to follow the Four-Phase Test pattern, using newlines to separate phases.
- Try to use
Gitlab.config.gitlab.hostrather than hard coding'localhost'
let variables
GitLab's RSpec suite has made extensive use of let variables to reduce
duplication. However, this sometimes comes at the cost of clarity,
so we need to set some guidelines for their use going forward:
-
letvariables are preferable to instance variables. Local variables are preferable toletvariables. - Use
letto reduce duplication throughout an entire spec file. - Don't use
letto define variables used by a single test; define them as local variables inside the test'sitblock. - Don't define a
letvariable inside the top-leveldescribeblock that's only used in a more deeply-nestedcontextordescribeblock. Keep the definition as close as possible to where it's used. - Try to avoid overriding the definition of one
letvariable with another. - Don't define a
letvariable that's only used by the definition of another. Use a helper method instead.
Time-sensitive tests
Timecop is available in our Ruby-based tests for verifying things that are time-sensitive. Any test that exercises or verifies something time-sensitive should make use of Timecop to prevent transient test failures.
Example:
it 'is overdue' do
issue = build(:issue, due_date: Date.tomorrow)
Timecop.freeze(3.days.from_now) do
expect(issue).to be_overdue
end
end
Test speed
GitLab has a massive test suite that, without parallelization, can take more than an hour to run. It's important that we make an effort to write tests that are accurate and effective as well as fast.
Here are some things to keep in mind regarding test performance:
-
doubleandspyare faster thanFactoryGirl.build(...) -
FactoryGirl.build(...)and.build_stubbedare faster than.create. - Don't
createan object whenbuild,build_stubbed,attributes_for,spy, ordoublewill do. Database persistence is slow! - Use
create(:empty_project)instead ofcreate(:project)when you don't need the underlying Git repository. Filesystem operations are slow! - Don't mark a feature as requiring JavaScript (through
@javascriptin Spinach orjs: truein RSpec) unless it's actually required for the test to be valid. Headless browser testing is slow!
Features / Integration
GitLab uses rspec-rails feature specs to test features in a browser environment. These are capybara specs running on the headless poltergeist driver.
- Feature specs live in
spec/features/and should be namedROLE_ACTION_spec.rb, such asuser_changes_password_spec.rb. - Use only one
featureblock per feature spec file. - 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.
Spinach (feature) tests
GitLab moved from Cucumber to Spinach for its feature/integration tests in September 2012.
As of March 2016, we are trying to avoid adding new Spinach tests going forward, opting for RSpec feature specs.
Adding new Spinach scenarios is acceptable only if the new scenario requires
no more than one new step definition. If more than that is required, the
test should be re-implemented using RSpec instead.
Testing Rake Tasks
To make testing Rake tasks a little easier, there is a helper that can be included
in lieu of the standard Spec helper. Instead of require 'spec_helper', use
require 'rake_helper'. The helper includes spec_helper for you, and configures
a few other things to make testing Rake tasks easier.
At a minimum, requiring the Rake helper will redirect stdout, include the
runtime task helpers, and include the RakeHelpers Spec support module.
The RakeHelpers module exposes a run_rake_task(<task>) method to make
executing tasks simple. See spec/support/rake_helpers.rb for all available
methods.
Example:
require 'rake_helper'
describe 'gitlab:shell rake tasks' do
before do
Rake.application.rake_require 'tasks/gitlab/shell'
stub_warn_user_is_not_gitlab
end
describe 'install task' do
it 'invokes create_hooks task' do
expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
run_rake_task('gitlab:shell:install')
end
end
end