Posted almost 7 years ago. Visible to the public.

RSpec in depth [2d]

Built-in matchers

Get an overview of all the matchers that are built into RSpec Archive .

Play with some of these matchers in your MovieDB tests.

The benefits of using better matchers

Which of the following two lines is better? Why?

Copy
expect(array).to include(5) expect(array.include?(5)).to eq(true)

Custom matchers

Write a custom matcher Archive called have_same_attributes_as. It should compare the attributes of two ActiveRecord instances:

Copy
movie1 = create(:movie, title: 'Foo', year: 2007, description: 'Lorem ipsum') movie2 = create(:movie, title: 'Foo', year: 2007, description: 'Lorem ipsum') movie3 = create(:movie, title: 'Bar', year: 2008, description: 'Lorem ipsum') expect(movie1).to have_same_attributes_as(movie2) # passes expect(movie1).to have_same_attributes_as(movie3) # Fails with 'Expected movie #112 to have same attributes as movie #113, but the attributes #title and #year differed'

Now write a method Movie#copy in your MovieDB. It saves a duplicate of the movie, copying all the attributes. Test this method using your new have_same_attributes_as matcher.

Tip

ActiveSupport gives your arrays a #to_sentence method that may help you build the error message:

Copy
['foo', 'bar', 'baz'].to_sentence => "foo, bar and baz"

Also see Where to put custom matchers and other support code.

More RSpec features

RSpec is a very advanced testing framework that can do much more than asserting two values are equal.

Browse through the following sub-projects of RSpec to get an overview of RSpec's capabilities:

Sharing test setup

You should know the following tools:

  • Nested example groups
  • before(:each)
  • after(:each)
  • let
  • subject

Sharing test setup can read to DRY, but tightly coupled test code. Read Prefer self-contained examples Archive for an argument for isolating tests instead, even if it means some duplication. In general it is more important for a test to be simply than to be DRY.

A sweet spot is often to prefer isolated tests where possible, but share test setup when it becomes excessively complicated or expensive. If we share setup, it is best to do within a shared context only. This way so we limit the setup's scope.

Copy
describe Klass do describe '#foo' do it 'does basic thing 1' do # isolated test without shared setup here end it 'does basic thing 2' do # isolated test without shared setup here end end context 'on the night of DST change in Australia' do before :each do # complicated, shared setup here end it 'handles special case 3' do # test using the shared setup end it 'handles special case 4' do # test using the shared setup end end end

Shared examples

Read the following:

Make the following change to your MovieDB:

  • A new tab "Changes" shows a log of recent changes made to movies and actors.

  • E.g. when a movie was created there is a log entry saying
    Movie "Sunshine" was created

  • E.g. when a movie was updated there is a log entry saying
    Movie "Sunshine" was updated

  • E.g. when an actor was destroyed there is a log entry saying
    Actor "Shohreh Aghdashloo" was destroyed

  • Use callbacks Archive to automatically write changelog entries to the database as a model record gets created, updated or destroyed.

  • Extract the logic into a module so it can be re-used by both Actor and Movie models.

    Tip

    The method producing the logged identifier may differ between models, e.g. Actor#full_name vs. Movie#title. You can either use the same method name here (like #to_s or #name_for_log), or build a parametrized module using Modularity Archive .

  • In your RSpec tests, use a shared example group to share tests between actor_spec.rb and movie_spec.rb.

Mocking

Through Mocking you can take control of the program under test. This lets you test unmocked code easier.

Earlier in this card you implemented a change log for MovieDB. Change your RSpec tests so they no longer write log entries to the database. Instead use mocks to test that log entries would have been written with the correct attributes.

Talk to your mentor about the pros and cons of mocking.

Different types of specs

Until now we have only written "model specs". These are classic "unit tests" where you instantiate an object, call a method and observe its return value or side effects.

Your MovieDB already uses the gem rspec-rails Archive which has many different types of specs. These help you take a close look at Rails components that are not easily instantiated, such as routes, views or controller actions.

Go through the rspec-rails Archive docs and get an overview what types of specs are supported.

Request spec

When your MoviesController#show cannot find a movie, it currently crashes with ActiveRecord::RecordNotFound. Change that so instead of crashing, it sets a flash "Movie not found" and redirects to the movie index.

Write a request spec Archive that takes a close look at MoviesController#show:

  • If the given ID was found, the view movies/show is rendered
  • If the given ID was not found, a redirect to /movies is returned. The movies index shows a flash message.

Tip

There is a render_template() matcher that helps with test above. To get this matcher, add a gem rails-controller-testing.

Tip

If you place your spec file in spec/requests you don't need the type: :request option Archive .

Helper spec

In the validations card we added a helper to display an error message.

Test that helper with a helper spec Archive .

Does your version of Ruby on Rails still receive security updates?
Rails LTS provides security patches for unsupported versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2).

Owner of this card:

Avatar
Henning Koch
Last edit:
25 days ago
by Henning Koch
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra Curriculum
This website uses short-lived cookies to improve usability.
Accept or learn more