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


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.


Reply via email to