On Wed, Dec 5, 2012 at 9:11 PM, David Chelimsky <[email protected]> wrote:
> 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

Here with better variable names:

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

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

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