Engineering

Our engineering is underpinned by principles that celebrate both the integrity of code and the ambition to innovate. This manifesto champions excellence, advocates for the adoption of cutting-edge technologies, and fosters an environment of continuous intellectual growth.

Testing guidelines

Here's a few general guidelines to walk you through your Rails testing journey:

• Test the app you're building, not the framework or library. There's no need to test if ActiveRecord validations work for example, when ActiveRecord itself already has tests for those. • TDD is not required, but tested code is. While some popular testing methodologies tell you to write tests first, this doesn't matter much in practice. What matters is that your code has useful tests. • Code coverage doesn't mean anything. You can never know all the edge cases. • Code should be easy to test. If it's hard to test, change your code. • Fast tests are happy tests.

RSpec

Avoid the should syntax

The should syntax is dead. Use the expect syntax. This also means no shoulda.

# ✗ Avoid user.name.should eq "Davy Jones" # ✓ OK expect(user.name).to eq "Davy Jones"

Running specific examples You can run specific examples by providing their line number when you run your tests.

rspec spec/models/user_spec.rb:11

Add a base .rspec config Prefer to run specs with the --color or -c and the --format=doc or -f d options. Store it in an .rspec file in the repo so it becomes the default.

# .rspec --color --format=doc

Find slow tests with profiling Running specs with the --profile or -p option will display the top ten slowest examples. It also accepts a number argument. Run this in your CI, but don't place it in .rspec.

# Displays the 5 slowest examples rspec spec/models/user_spec.rb -p 5

Use shared_examples When faced with repetitive test code, consider refactoring them to use RSpec.shared_examples. https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples

shared_examples "some example" do |param| ... end describe "my tests" do include_examples "some_example", "param1" end

describe

Consider examples as the project's documentation.

Use proper descriptions

Describe your methods properly and accurately. If you change what the code does, do NOT forget to update the test's description.

Top level blocks Your top level describe block should indicate the class under test.

# This is actually a smoke test describe Venue do ... end

Avoid instance variables

Use let or let! instead of using @instance_variables. If in doubt, use let!.

Move your befores up top

before, around, after and let blocks should come right after a describe block and before the next describe or context block.

Use correct method prefixes Prefix your methods with # if it's an instance method and . if it's a class method or model scope.

describe "#instance_method" describe ".class_method"

context

Use context for branching code. Prefer using with and without or when.

describe "#has_comments?" do context "in a post with comments" do let!(:comment) { create :comment, post: post } it "returns true" do expect(post.has_comments?).to eq true end end context "in a post without comments" do it "returns false" do expect(post.has_comments?).to eq false end end

it

One expectation per block

Prefer to only have one expectation per it block.

Be explicit with descriptions Say what the code does, not what it should do. (aka: avoid starting descriptions with "should".)

# ✓ OK it "returns true" it "sends the email" # ✗ Avoid it "should be true" it "should send the email"

Use eq, not be Prefer to use eq over be and magic matchers.

# ✓ OK expect(user.admin?).to eq true expect(user.age).to eq 12 # ✗ Avoid expect(user).to be_admin expect(user.age).to be 12

Multiline blocks

Prefer multiline blocks over one liners. If you must use one liners, use is_expected. Again, avoid using the shoulda gem.

Pending tests Mark incomplete tests with pending.

it "publishes the event" do pending "because reasons" event.publish! expect(event.published?).to eq true end

For methods that don't have tests yet (but should), use an it with no block. Don't use pending.

# ✗ Avoid describe "#untested_method" do pending "because reasons" end # ✓ OK it "#untested_method" # ❤ BETTER describe "#untested_method" do it "succeeds with valid data" it "returns nil when given invalid options" end

Metaprogramming tests

Don't.

Rails

• Follow the rspec-rails directory structure and naming conventions. • cucumber can be cucumbersome. Use RSpec features specs with capybara instead. • Delegate routing and view specs to features specs. • Only write controller specs for actions that cannot be covered by features specs. Otherwise, write features specs. • Use requests or api specs for testing API requests. • Do not forget to test helpers and rake tasks. • Prefer spec_helper over rails_helper. Only require rails_helper for tests that need Rails to work. • Always include the database_cleaner gem or it's faster cousin, database_rewinder. • Use factorybotrails over fixtures. • In your rails_helper, always set config.use_transactional_fixtures to false. This isn't necessary if you're using database_rewinder. • External HTTP requests are slow. Always mock them or use gems like vcr and WebMock. • When mocking/stubbing, never mock/stub the unit you are testing.

# ✗ Avoid allow(User).to receive(:count).and_return(2) expect(User.count).to eq 2

Model specs

• No mocking. Test the actual models. • Skip association tests. • Test validations. Presence validation specs are optional. # Validations expect(valid_project.errors.full_messages).to eq [] expect(invalid_project.errors.full_messages).to include "Video url is not a valid Youtube/Vimeo url" • Test scopes for both inclusion and exclusion. # Scopes expect(Project.published).to include published_project expect(Project.published).to_not include unpublished_project • Callbacks testing can be done in a couple of ways. # Assumes an `#ensure_default_role` callback that sets the default role if not provided # You can use a proc user.role = nil expect { user.save }.to change(user, :role).to("default") # or test the effect user.role = "admin" user.save expect(user.reload.role).to eq "admin" # or test for the just the call expect_any_instance_of(User).to receive(:ensure_default_role) user.save # then test the functionality user.nil user.send :ensure_default_role expect(user.role).to eq "default" • Testing private methods can be optional, but only if there is a public method spec that covers it.

View specs

Don't. Prefer to use this time to write feature specs instead.

Controller specs

• See Features: https://www.mashupgarage.com/playbook/#feature-specs

Lib specs

• Unless you autoloaded the file you're testing, you will need to require it. • Do not require rails_helper if you can avoid it. Mock what you can.

Helper specs

• RSpec provides a helper object so you don't have to include the module in a dummy class to test it.

expect(helper.image_url_for user).to eq "http://wat.com/img/fifi"

Mailer specs

• Always mock the models. • Do not test #deliver_now. • Test for addresses, subject and attachments.

expect(mailer.to).to eq recipient.email expect(mailer.from).to eq sender.email expect(mailer.reply_to).to eq sender.reply_to_email expect(mailer.attachments.size).to eq 1

Request specs

• Request specs are considered as integration tests. Treat them as such. • Only use these specs for testing APIs. Use Features for pages.

• Test the body and status in the same example.

it "returns the correct user" do get user_path(user) expect(response.body["id"]).to eq user.id expect(response.status).to eq 200 end

• Use helpers to reduce duplication. For example, assuming you're working on a JSON API, this helper will clean up the parsing logic for you.

# support/api_helpers.rb module ApiHelpers def json JSON.parse(response.body) end end # rails_helper.rb config.include ApiHelpers, type: :request # usage expect(json["id"]).to eq user.id

Feature Specs

• Use feature and scenario blocks. feature "User Authentication" do scenario "with valid credentials" do ... end end • Prefer to test for element visibility instead of testing model effects when possible. expect(page).to have_link "Log Out" # ✗ Avoid expect(user.signed_in?).to eq true # This should be in a unit test • Try to be more selective of tests. Using expect(page).to have_content will lead to really long error messages. # ✗ Avoid expect(page).to have_content "Log Out" # ✓ OK expect(page).to have_selector '#nav', text: /Log Out/ # ❤ BETTER, if it works within '#nav' do expect(page).to have_content "Log Out" end • Multiple expectations per scenario is encouraged. Each scenario takes time to spin up, so using less scenarios means faster tests. • Helper methods inside feature blocks or within the spec file is allowed. • Limit the usage of the js: true tag only to blocks that have javascript interaction. • Prefer selenium that uses the chrome driver. Avoid poltergeist and capybara-webkit. • There will be cases where you will be needing selenium to see what's going on. This snippet provides you with a selenium: true tag. # Gemfile gem 'capybara' gem 'selenium-webdriver' gem 'chromedriver-helper' # <- New! # rails_helper.rb Capybara.register_driver :selenium do |app| Capybara::Selenium::Driver.new(app, browser: :chrome) end Capybara.javascript_driver = :chrome config.before(:each) do |example| Capybara.current_driver = :selenium if example.metadata[:selenium] end • Don't use sleep in your specs. If you feel like you really need to, ask first. • Use find_button('Save').trigger('click') instead of click_button('Save') for javascript enabled specs.

Next: Design Playbook

Connect.

Mashup Garage, a premier software development team, specialises in crafting exceptional products for startups and enterprises. With expertise in React, Elixir/Phoenix, and Ruby on Rails, we deliver solutions that meet your unique needs. Our mission is to bring value backed by decades of technical expertise and global co-founding experience.

What do you need help with?

Build a project

Build a team

Consult

Speak to someone

Expect a guaranteed response from us in 1-2 business days.

United Kingdom

London

Islington, London

+44 738 777 3405

LDN

Philippines

Manila

3F Topy IV Building, 3 Economia Road, Bagumbayan, Quezon City, 1110

+63 917 3084089

MNL