On Wed, Dec 5, 2012 at 8:05 PM, Chris Bloom <[email protected]> wrote:
> BTW: Thank you for your feedback so far!
>
>
> On Wed, Dec 5, 2012 at 9:04 PM, Chris Bloom <[email protected]> wrote:
>>
>> I like the idea of breaking it out into small sub-tests, but I think both
>> of the issues I mentioned previously would still be present. That is, it
>> doesn't appear that the before block is executed for macros,

If your macro generates examples (unlike your first email this thread)
the before hooks will be run before the examples are run.

>> so instance
>> variables from my setup code aren't available, and if I write it as a
>> matcher I still won't get the correct test result messages. I need to test
>> this behavior for an indeterminate number of API endpoints, which is why I
>> went with a macro in the first place.

You can wrap my previous suggestion in a shared example group and get
something closer to what you're looking for:

shared_examples "minimum params" do |*args|
  url = description

  it "is valid with min params" do
    get url, args.inject({}) {|h, k| h.merge(k => "")}
    expect(response.status).to eq(200)
    expect(response.body).not_to include("missing parameter:")
  end

  args.each do |p|
    it "requires #{p} in params" do
      get url, (args - [p]).inject({}) {|h, k| h.merge(k => "")}
      expect(response.status).to eq(400)
      expect(response.body).to include("missing parameter: #{p}")
    end
  end
end

describe "API" do
  describe "/api/v1/foo" do
    include_examples "minimum params", "k1", "k2"
  end

  describe "/api/v1/bar" do
    include_examples "minimum params", "k3", "k4", "k5", "k6"
  end
end

This outputs as follows:

$ rspec example_spec.rb -cfd

API
  /api/v1/foo
    is valid with min params
    requires k1 in params
    requires k2 in params
  /api/v1/bar
    is valid with min params
    requires k3 in params
    requires k4 in params
    requires k5 in params
    requires k6 in params

WDYT?

>>
>>
>> On Tue, Dec 4, 2012 at 4:21 PM, David Chelimsky <[email protected]>
>> wrote:
>>>
>>> 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.
>>>
>>>
>>
>
> --
> 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.
>
>

-- 
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