6

I'm trying to create a custom matcher for my tests in RoR using RSpec.

  define :be_accessible do |attributes|
    attributes = attributes.is_a?(Array) ? attributes : [attributes]
    attributes.each do |attribute|
      match do |response|
        response.class.accessible_attributes.include?(attribute)
      end
      description { "#{attribute} should be accessible" }
      failure_message_for_should { "#{attribute} should be accessible" }
      failure_message_for_should_not { "#{attribute} should not be accessible" }
    end
  end

I want to be able to write something like the following in my tests:

...
should be_accessible(:name, :surname, :description)
...

but with the matcher defined above, I must pass an array of symbols instead of symbols separated by commas otherwise the test examines only the first symbol.

Any ideas?

Lazarus Lazaridis
  • 5,803
  • 2
  • 21
  • 35
  • Here is an answer that should match your very first need : http://stackoverflow.com/a/4643289/582863. Anyway, I'm curious about your intention here... Do you just want to reduce the number of lines in your rspec test files, or are your testing complex accessibilities on model attributes? – Saaman Apr 21 '13 at 21:44
  • The problem with the link you provided is that this is not a "regular" def method so I can't use the *. Answering your question, I just want to reduce my rspec's lines :) – Lazarus Lazaridis Apr 21 '13 at 21:51

1 Answers1

4

I made it work this way :

RSpec::Matchers.define :be_accessible do |*attributes|
  match do |response|

    description { "#{attributes.inspect} be accessible" }

    attributes.each do |attribute|
      failure_message_for_should { "#{attribute} should be accessible" }
      failure_message_for_should_not { "#{attribute} should not be accessible" }

      break false unless response.class.accessible_attributes.include?(attribute)
    end
  end
end

I inverted the match and the each loop. I think this is the way Rspec expect it to be, as the block given to the match method is the one executed by Rspec abstract matcher (I guess).

By defining the block with |*attributes|, it takes the list of parameters and turn it into an Array.

So calling should be_accessible(:name, :surname, :description) will work.

By the way, if you just want to check for the existence of attributes, a simple

should respond_to(:name, :surname, :description)

works as well. But it does not looks like for mass-assignement aspect.

Saaman
  • 598
  • 4
  • 14
  • It works, thanx for your time! I want to check for mass assignment so your solution is the one I need. – Lazarus Lazaridis Apr 22 '13 at 10:34
  • This really helped me however, when I adapted this example for my own situation I found it only works for positive expectations (does not work for should_not as-is). I had to create separate `match_for_should` and `match_for_should_not` blocks like detailed here: https://www.relishapp.com/rspec/rspec-expectations/v/3-0/docs/custom-matchers/define-matcher#matcher-with-separate-logic-for-should-and-should-not – manafire Jan 02 '14 at 06:56