5 min read

Testing with RSpec

Testing with RSpec
Photo by Sigmund / Unsplash

In the first part of this series, we covered why testing code is an essential part of the software development process, helps with catching bugs early, improves the overall design and architecture of software, and increases communication between engineering and product teams.

I often hear from software engineers that tests are complex and difficult to write, and they are often unsure of which tests to write too.

In this second part, we will get an overview of the different types of tests we can write for an application, what RSpec is, what its ecosystem is, how a basic RSpec test looks, and how to run tests.

General concepts of tests

Before looking into how RSpec can help us write tests, we need to review the different types of tests that can be written for an application. Here is a list of 8 types of tests we can write in a web application.

  1. Unit Tests: These are tests that verify the functionality of a single unit of code, such as a method or function, in isolation from the rest of the system.
  2. Integration Tests: These tests verify the interactions between different code units, such as multiple methods or services, to ensure they work together as expected.
  3. Feature Tests: These tests verify the behavior of a larger feature or functionality, often involving multiple system components.
  4. Controller Tests: These are tests that verify the behavior of controllers in a web application, such as handling HTTP requests and responses.
  5. Request Tests: These tests verify the behavior of individual HTTP requests in a web application, often focusing on the routing and rendering of views.
  6. Model Tests: These tests verify the behavior of database models in a Rails application, such as validating data and interacting with the database.
  7. View Tests: These tests verify the behavior of views in a web application, such as rendering HTML and handling form submissions.
  8. System Tests: These are tests that verify the behavior of an entire application from end to end, often involving interactions between multiple systems and components.

By using a combination of these test types, developers can create a comprehensive suite of tests that cover all aspects of their codebase and ensure that their software works as intended.

RSpec, as a complete testing library, can help us write all those kinds of tests. It also integrates with many complementary tools allowing the team to leverage a complete ecosystem to not only test well but also avoid having to write a lot of code to do so.

RSpec : a testing library for Ruby-written software

RSpec is one of the most used testing libraries in the Ruby ecosystem, minitest being the second. Both serve a great purpose in this ecosystem: testing the rest of the code.

While minitest is a great library we prefer to rely on RSpec and this series of articles will focus on it.

RSpec design is focused on Behavior Driven Design (BDD) and helps to test what the code is doing rather than its implementation. This transpires through its Domain-Specific Language (DSL), which is very expressive and close to "regular" English.

For newcomers to testing with RSpec, here are a few essential points we like about RSpec:

  1. Ability to easily mock and stub dependencies, making tests more isolated and less prone to failure due to external factors.
  2. Built-in support for various test types, including unit tests, functional tests, and integration tests.
  3. Support for various matchers and expectations, making it easy to test various behaviors.
  4. Built-in support for test suites and groups, allowing for easy organization and execution of tests.

For engineers familiar with testing already, here are a few additional benefits they are probably looking for:

  1. Improved readability and organization of tests, making it easier to understand and maintain the test suite.
  2. Built-in support for test-driven development (TDD), allowing for a more efficient development process.
  3. Built-in support for test metadata, such as tags and descriptions, making it easy to filter and organize tests.
  4. Support for parallel test execution, allowing for faster test runs.

The gem and its ecosystem

The main gem rspec doesn't exist. It's a metagem that includes the rspec-core, rspec-expecations and rspec-mocks gems. This is enough to get started and write tests. It also allows you to pick and choose complimentary gems to handle mocking, for example.

For developers working on a Ruby on Rails application, the rspec-rails gem is probably needed. By itself, it adds a lot of specific matchers to use with the Ruby on Rails components. But it will also require the rspec-core, rspec-expecations, rspec-mocks, rspec-support gems.

A search on Rubygems.org will show you how much there is around RSpec itself beyond those.

A syntax overview

RSpec tests are written in a very "English-like" DSL: we describe a subject of the test within a given context and then provide examples using matchers to compare an actual value with the expected one.

describe User do
  describe '#name' do
    context 'when the user has a first name and last name' do
      subject { User.new(first_name: 'John', last_name: 'Smith') }

      it { expect(subject.name).to eq('John Smith') }
    end
  end
end

Even if you are unfamiliar with Ruby's syntax, you should be able to understand that: for the User class, given an instance of that class with a first name "John" and a last name "Smith". The return value of its instance method name, should be "John Smith".

A few notes here:

  1. describe is used to group tests together
  2. subject is used to define what is being tested
  3. it is used to define an example

Running tests

RSpec tests are saved and stored within files in the spec/ folder of the project usually. They are also sorted by types (unit, functional, system, ...), thus allowing software engineers to easily find the test files they need when working on any part of the application.

RSpec comes with a command line utility to run tests either as a whole, for a specific file or just to run one test within one file.

# running all models specs
$> rspec spec/models
# running only the user specs
$> rspec spec/models/user_spec.rb
# running only the user spec containing the line 42
$> rspec spec/models/user_spec.rb:42
You can also use tags to target only examples tagged with a specific tag. See https://relishapp.com/rspec/rspec-core/v/2-4/docs/command-line/tag-option for details.

Wrapping up: RSpec as a complete, approachable testing library

In conclusion, RSpec is not only covering all the angles of testing an application, but it also lets you do so with a DSL that is extremely readable and encourages software engineers to write good, maintainable code too.

It's also very easy to structure the whole test suite within the project folders and run tests locally and within a CI pipeline.

Ready to adopt it?

From our experience training engineers, it's clear that RSpec's syntax and features make it a very easy-to-pick-up tool to implement a complete testing strategy for a Ruby application.

Do you want to check further details about RSpec? Do you want to learn how to write tests, including mock and stub code? You can download our introduction Ebook on RSpec.

Depending on the type of application and the context of features or parts of the code base, different types of tests can be used. Deciding on the testing strategy and implementing it are among the topics we cover in our RSpec workshops and mentoring programs.
If you want to organize such a session or program for yourself or your team, you can schedule a call as early as today!