Didn't realize you were trying to do so much in one statement.

The idea of a matcher, custom or built-in, is that the match block has
one expectation expressed as a boolean - it should return true or
false to indicate pass or fail. This one matcher is wrapping 10
different expectations in logical pairs. I'd probably start with 5
examples with two expectations in each:

it "is valid with the minimum params" do
  get "/api/v1/originator/hello", @minimum_params
  expect(response.status).to eq(200)
  expect(response.body).not_to include("missing parameter:")
end

it "requires api_key in params" do
  get "/api/v1/originator/hello", @minimum_params.except("api_key")
  expect(response.status).to eq(400)
  expect(response.body).to include("missing parameter: api_key")
end

# 3 more failure cases

Each example has two expectations, but they work together to specify
different parts of the same outcome, so I'm comfortable bypassing the
one-expectation-per-example guideline.

You could, conceivably, reduce some of the duplication with a custom
matcher that just deals with one parameter - something like:

it { should require_param("api_key") }

Either that or wrap the failure examples in an an iterator:

describe "minimum params" do
  MIN_PARAMS = {
    api_key:     "",
    nonce:       "",
    timestamp:   "",
    hmac_digest: "
  }

  MIN_PARAMS.each_pair do |k, v|
    it "requires api_key in params" do
      get "/api/v1/originator/hello", MIN_PARAMS.except(k)
      expect(response.status).to eq(400)
      expect(response.body).to include("missing parameter: #{k}")
    end
  end
end

WDYT?

On Tue, Dec 4, 2012 at 2:17 PM, Chris Bloom <[email protected]> wrote:
> I've run into another set of problems with the two solutions you suggested.
>
> If I go the first way, having the macro method define the example inside of
> it, and call that from within a describes block, it appears that any
> instance variables declared in the before block of the spec aren't
> available. Is this correct behavior?
>
> Alternately, if I instead turn it into a matcher and call it from inside an
> it{} block, I'm not able to get proper result messages.
>
> Granted the latter problem is easily ignorable given that the test itself
> works, but I'd like to understand both problems anyway.
>
> Here's the code both ways, first, as a matcher:
>
> # spec/support/api_macros.rb
> RSpec::Matchers.define :require_minimum_request_params do |url, params|
>   match do |_|
>     get url
>     response.status.should == 400
>     # See https://github.com/dchelimsky/rspec/issues/25
>     response.body.include?("missing parameter:")
>
>     (params.length - 1).times do |i|
>       params.to_a.combination(i+1).each do |c|
>         get url, Hash[*c.flatten]
>         response.status.should == 400
>         response.body.include?("missing parameter:")
>       end
>     end
>
>     get url, params
>     response.status.should == 200
>     !response.body.include?("missing parameter:")
>   end
>
>   failure_message_for_should do
>     "expected URL #{url} to require #{params.keys.join(', ')} as the minimum
> parameters"
>   end
>
>   failure_message_for_should_not do
>     "expected URL #{url} to not require #{params.keys.join(', ')} as the
> minimum parameters"
>   end
>
>   description do
>     "require minimum parameters #{params.keys.join(', ')} for requests to
> URL #{url}"
>   end
> end
>
> # spec/requests/api/api_v1.rb
> describe MyApp::API_v1 do
>   before do
>     @minimum_params = {
>       api_key:     "",
>       nonce:       "",
>       timestamp:   "",
>       hmac_digest: ""
>     }
>   end
>
>   context "originator" do
>     describe "GET /api/v1/originator/hello" do
>       it { should require_minimum_request_params("/api/v1/originator/hello",
> @minimum_params) }
>     end
>   end
> end
>
> # $ rspec spec/requests/api/api_v1_spec.rb
> MyApp::API_v1
>   originator
>     GET /api/v1/originator/hello
>       should == 200
>
> And instead as a macro:
> # spec/support/api_macros.rb
> module ApiMacros
>   def self.included(base)
>     base.extend(ClassMethods)
>   end
>
>   module ClassMethods
>     def it_should_require_minimum_request_params(url, params)
>       it "should require minimum request params" do
>         get url
>         response.status.should == 400
>         response.body.should include("missing parameter")
>
>         (params.length - 1).times do |i|
>           params.to_a.combination(i + 1).each do |c|
>             get url, Hash[*c.flatten]
>             response.status.should == 400
>             response.body.should include("missing parameter")
>           end
>         end
>
>         get url, params
>         response.status.should == 200
>         response.body.should_not include("missing parameter")
>       end
>     end
>   end
> end
>
> # spec/requests/api/api_v1.rb
> describe MyApp::API_v1 do
>   before do
>     @minimum_params = {
>       api_key:     "",
>       nonce:       "",
>       timestamp:   "",
>       hmac_digest: ""
>     }
>   end
>
>   context "originator" do
>     describe "GET /api/v1/originator/hello" do
>       it_should_require_minimum_request_params("/api/v1/originator/hello",
> @minimum_params)
>     end
>   end
> end
>
> # $ rspec spec/requests/api/api_v1_spec.rb
> Failure/Error: (params.length - 1).times do |i|
>      NoMethodError:
>        undefined method `length' for nil:NilClass
>      # ./spec/support/api_macros.rb:13:in `block in
> it_should_require_minimum_request_params
>
> On Monday, December 3, 2012 5:42:08 PM UTC-5, Chris Bloom wrote:
>>
>> Ah, OK. I see the difference now. Thanks for the clarification.
>>
>> On Monday, December 3, 2012 3:33:42 PM UTC-5, [email protected] wrote:
>>>
>>> On Mon, Dec 3, 2012 at 2:10 PM, Chris Bloom <[email protected]> wrote:
>>> > I'm trying to refactor some common code used in a bunch of requests
>>> > specs
>>> > into a macro, but every way I've tried so far ends in an error saying
>>> > it
>>> > can't find the macro method, or if it can then it can't find the `get`
>>> > method. Can someone point me to an example of how to do this?
>>> >
>>> > # spec/requests/api/api_v1.rb
>>> > describe MyApp::API_v1 do
>>> >   context "originator" do
>>> >     describe "GET /api/v1/originator/hello" do
>>> >       it_should_check_minimum_protected_api_params
>>> > "/api/v1/originator/hello"
>>> >     end
>>> >   end
>>> > end
>>> >
>>> > # spec/support/api_macros.rb
>>> > module ApiMacros
>>> >   def self.included(base)
>>> >     base.extend(GroupMethods)
>>> >   end
>>> >
>>> >   module GroupMethods
>>> >     def it_should_check_minimum_protected_api_params(url)
>>> >       get url
>>> >       ...
>>> >     end
>>> >   end
>>> > end
>>> >
>>> > # spec/spec_helper.rb
>>> > RSpec.configure do |config|
>>> >   config.include ApiMacros, :type => :request
>>> > end
>>> >
>>> > This ends in:
>>> >
>>> > $ rspec spec/requests/api
>>> > /spec/support/api_macros.rb:8:in
>>> > `it_should_check_minimum_protected_api_params': undefined method `get'
>>> > for
>>> > #<Class:0x000001036708f0> (NoMethodError)
>>>
>>> That's not saying it can't find the macro method. It says it can't find
>>> `get`.
>>>
>>> The macro is being evaluated at the class level, whereas "get" is an
>>> instance method. The macro needs to define examples that use the get
>>> method, e.g:
>>>
>>> def it_should_check_minimum_protected_api_params(url)
>>>   it "should check minimum protected api params" do
>>>     get url
>>>     # ...
>>>   end
>>> end
>>>
>>> HTH,
>>> David
>
> --
> You received this message because you are subscribed to the Google Groups
> "rspec" group.
> To post to this group, send email to [email protected].
> To unsubscribe from this group, send email to
> [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msg/rspec/-/R3BPlOkxEZIJ.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"rspec" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to