Topics: RSpec

Repeats

How to stub class constants in RSpec

Hint: There's another card with this helper for Cucumber features.


Sometimes you feel like you need to stub some CONSTANT you have defined in an other class. Since actually constants are called constants because they're constant, there's no way to easily stub a constant.

Here are three solutions for you.

Easiest solution

Rethink! Do you really need CONSTANT = %w[foo bar] to be constant? In many cases, setting it as a sim…

Repeats

Custom error messages in RSpec or Cucumber steps

Sometimes you have a test expectation but actually want a better error message in case of a failure. Here is how to do that.

Background

Consider this test:

User.last.should be_present

In case of an error, it will fail with a not-so-helpful error message:

expected present? to return true, got false (Spec::Expectations::ExpectationNotMetError)

Solution

That can be fixed easily. RSpec expectations allow you to pass an error message like this:

User.last.should be_present, 'Could not find a user!'

Now your test will…

Custom RSpec matcher for allowed values

In contrast to the included allow_value matcher, the attached matcher will also work on associations, which makes it ideal for testing assignable_values. Use it like this:

``` describe Unit do

subject { FactoryGirl.build(:unit) }

describe '#building' do

it 'should only allow buildings that a user has access to' do
  user = FactoryGirl.build(:user)
  granted_building = FactoryGirl.create(:building)
  denied_building = FactoryGirl.create(:building)

  Power.with_power(Power.new(user)) do
    Power....
External contentRepeats

Helper methods - RSpec Core

You can define methods in any example group using Ruby's def keyword or define_method method. These helper methods are exposed to examples in the group in which they are defined and groups nested within that group, but not parent or sibling groups.

Controller specs do not persist the Rails session across requests of the same spec

In specs, the session never persists but is always a new object for each request. Data put into the session in a previous request is lost. Here is how to circumvent that.

What's going on?

You are making ActionController::TestRequests in your specs, and their #initialize method does this:

self.session = TestSession.new

This means that each time you say something like "get :index", the session in your controller will just be a new one, and you won't see …

External content

grosser/rspec-instafail

Show failing specs instantly. Show passing spec as green dots as usual.

Configuration:

# spec/spec.opts (.rspec for rspec 2)
--require rspec/instafail
--format RSpec::Instafail
Repeats

Write custom RSpec matchers

There are three ways to define your own RSpec matchers, with increasing complexibility and options:

1. Use Spec::Matchers.define

Spec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
end

This is automatically available in all your specs when the code is loaded. We have a note on where to put custom RSpec matchers.

  1. Use simple_matcher (deprecated)

————–…

Repeats

RSpec matcher to check if two numbers are the same

Our rspec_candy gem has a matcher that will compare Fixnums (integers), Floats and BigDecimals with each other:

deal.total_price.should be_same_number_as(1200.99)

RSpec: Where to put custom matchers and other support code

Custom matchers are a useful RSpec feature which you can use to DRY up repetitive expectations in your specs. Unfortunately the default directory structure generated by rspec-rails has no obvious place to put custom matchers or other support code.

I recommend storing them like this:

spec/support/matchers/be_allowed_access.rb
spec/support/matchers/be_denied_access.rb
spec/support/blueprints.rb

To make this support code availabl…

How to find out which type of Spec you are

When you need to find out in which kind of spec you are during run-time, it's definitely possible. It's a lot easier in RSpec2.

For example, consider this global before block where you'd want to run some code for specific specs only:

config.before do
  # stuff
  that_fancy_method
  # more stuff
end

RSpec 2

If you want to run such a block for a specific type of specs, you can use filters:

config.before do
  # stuff
  # more stuff

How to set the user agent in tests

The User-Agent HTTP header identifies the client and is sent by "regular" browsers, search engine crawlers, or other web client software.

Cucumber

In Rack::Test, you can set your user agent like this on Capybara:

Given /^my user agent is "(.+)"$/ do |agent|
  page.driver.browser.header('User-Agent', agent)
  # Or, for older Capybaras:
  # page.driver.header('User-Agent', agent)
end

For Selenium tests with Firefox, it seems you can set the general.useragent.override profile setting to your preferred value. [See StackOverflow…

RSpec: Change the type of a spec regardless of the folder it lives in

In a Rails application, *_spec.rb files get special treatment depending on the file's directory. E.g. when you put a spec in spec/controllers your examples will have some magic context like controller, post or get that appears out of nowhere.

If you want that magic context for a spec in another folder, use the :type option:

describe CakesController, :type => :controller do
  ...
end
Repeats

RSpec: Stubbing a method that takes a block

If you stub a method or set expectations with should_receive these stubbed methods may also yield blocks. This is handy if the returning object is receiving a block call.

Consider this, where you cannot say and_return [] because of the block:

def crawl_messages
  Message.find_in_batches do |messages|
    messages.each(&:crawl)
  end
end

It works similar to and_return – just use and_yield:

describe '#crawl_messages' do
  it 'should proces...

Rails 3/4: How to add routes for specs only

If you want to have routes that are only available in tests (e.g. for testing obscure redirects), you can use the with_routing helper – but that one destroys existing routes which may break a specs that require them to work.

To keep both "regular" and test routes, do this:

class MyApplicationController < ActionController::Base
  def show
    render text: 'Welcome to my application'
  end
end

test_routes = Proc.new do
  get '/my_application' => 'my_application#show'
end
Rails.application.routes.ev...

RSpec: Where to put shared example groups

Shared example groups are a useful RSpec feature. Unfortunately the default directory structure generated by rspec-rails has no obvious place to put them.

I recommend storing them like this:

spec/models/shared_examples/foo.rb
spec/models/shared_examples/bar.rb
spec/models/shared_examples/baz.rb
spec/controllers/shared_examples/foo.rb
spec/controllers/shared_examples/bar.rb
spec/controllers/shared_examples/baz.rb

To ma…

How to find out if you are in Cucumber or in RSpec

Sometimes you need a piece of code to do something different for specs than for features. If you don't have separate environments, you can't check your Rails.env.

I managed to distinguish between specs and features by asking Capybara:

if defined?(Capybara) and Capybara.respond_to?(:current_driver)
  # you're in a Cucumber scenario
else
  # you're probably in a spec
end

You could omit the defined?(Capybara) condition, if you are sure that Capybara is always defined when that code is being called.

Repeats

ActiveRecord scopes must be loaded before using RSpec's "=~" matcher

To test whether two arrays have the same elements regardless of order, RSpec gives you the =~ matcher:

actual_array.should =~ expected_array

If either side is an ActiveRecord scope rather than an array, you should call to_a on it first, since =~ does not play nice with scopes:

actual_scope.to_a.should =~ expected_scope.to_a

Machinist blueprints do not work with a column :display

This didn't work for me. Seems display is already taken in Machinist.

# in spec/support/blueprints.rb
Partner.blueprint do
   company_name
   display { true }
end
80 cards