175 RSpec in depth [2d]

Updated . Posted . Visible to the public.

Matchers

Built-in matchers

Get an overview of all the matchers that are built into RSpec Show archive.org snapshot .

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?

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

Custom matchers

Write a custom matcher Show archive.org snapshot called have_same_attributes_as. It should compare the attributes of two ActiveRecord instances:

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:

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

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

DRY specs

Goals

You should know the following tools:

  • Shared example groups
  • Nested example groups
  • before(:each)
  • after(:each)
  • let
  • subject
  • RSpec.configure, config.before, config.after

Resources

Exercise: Shared examples

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 Show archive.org snapshot 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 Show archive.org snapshot .

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

DRY vs. coupling

Sharing test setup can lead to DRY, but tightly coupled test code. Read Prefer self-contained examples Show archive.org snapshot for an argument for isolating tests instead, even if it means some duplication. In general it is more important for a test to be simple 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.

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

    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

end

Mocking

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

Resources

Exercise

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.

Discussion

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

  • Can you imagine a reason why the mocking test could pass, but the code is broken?
  • Can you imagine a reason why the mocking test could fail, but the code is correct?

Spec types

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 Show archive.org snapshot 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 Show archive.org snapshot 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 Show archive.org snapshot 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 HTTP status 307 is used for the redirect. 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 Show archive.org snapshot .

Helper spec

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

Test that helper with a helper spec Show archive.org snapshot .

Henning Koch
Last edit
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra Curriculum (2015-09-04 13:24)