Problem with Custom matcher and Blocks


#1

Hi,

I’m trying to write my first custom matcher.

Here’s a bit of my example group.

describe “/contact/index” do
include FormMatchers

before(:each) do
render ‘contact/index’
end

it “should show the contact form” do
response.should have_a_contact_form
end

describe “the contact form” do
context “before it has been submitted” do
it “should have a subject dropdown box” do
response.should have_a_contact_form do |form|
form.should have_a_subject_field
end
end # it “should have a subject dropdown box”

  # This should be failing
  it "should have a name field" do
    response.should have_a_contact_form do |form|
      form.should have_a_name_field
    end
  end # it "should have a name field"

  describe "the subject dropdown box" do
    it "should have a feedback option" do
      response.should have_a_contact_form do |form|
        form.should have_a_subject_field do |subject|
          subject.should have_selector('option', :content => 

‘Feedback’)
end
end
end # it “should have a feedback option”

    # Etc...

  end # describe "the subject dropdown box"

end # context "before the form has been submitted"

end # Describe “the contact form”

end # describe “/contact/index”

Right now, everything before the describe “the subject dropdown box” is
passing, even though the one testing the name field should not because
I’ve
not added that field yet.

When it gets to the describe block for the subject, I get:

NoMethodError in ‘/contact/index the contact form before it has been
submitted the subject dropdown box should have a blank option’
undefined method `have_selector’ for
#FormMatchers::HaveAFormWithID:0x7f3260ac

The have_a_contact_form method is as follows:

def have_a_contact_form &block
have_a_form_with_id ‘contact’, &block
end

That calls a have_a_form_with_id method, which calls the haveAFormWithID
class.

module FormMatchers

class HaveAFormWithID

def initialize id, &block
  @id = id
  @block = block
end

def matches? response
  response.should have_selector('form#%s' % [@id]) do |form|
    !@block or @block.call form
  end
end

def description
  "have a form with id #{@id}"
end

def failure_message
  "expected to have a form with ID #{@id}"
end

def negative_failure_message
  "expected not to have a form with ID #{@id}"
end

end

def have_a_form_with_id id, &block
HaveAFormWithID.new id, &block
end

end

Sorry for all the code. Again this is my first custom matcher, so I
could be
doing something very wrong.

Any help much appreciated.

Thanks,
Brandon


#2

On 2 Apr 2009, at 02:25, Brandon O. wrote:

render ‘contact/index’
form.should have_a_subject_field
describe “the subject dropdown box” do

passing, even though the one testing the name field should not
because I’ve
not added that field yet.

When it gets to the describe block for the subject, I get:

NoMethodError in ‘/contact/index the contact form before it has been
submitted the subject dropdown box should have a blank option’
undefined method `have_selector’ for
#FormMatchers::HaveAFormWithID:0x7f3260ac

#have_selector is part of webrat. Have you required the appropriate
files so that method is visible to your new matcher class?

module FormMatchers
!@block or @block.call form

end


rspec-users mailing list
removed_email_address@domain.invalid
http://rubyforge.org/mailman/listinfo/rspec-users

As a P.S. I hate to take the wind out of your sails here, but I was
reflecting only today how, after 9 months of using RSpec to TDD our
Rails app, we have massaged the view specs down to almost nothing. IMO
99% of the time you should be writing a Cucumber feature instead and
leaving it at that.

Matt W.
http://beta.songkick.com
http://blog.mattwynne.net


#3

On Thu, Apr 2, 2009 at 3:55 PM, Matt W. removed_email_address@domain.invalid wrote:

include FormMatchers
context “before it has been submitted” do
end

Right now, everything before the describe “the subject dropdown box” is

class.
def matches? response
“expected to have a form with ID #{@id}”
end
Brandon
time you should be writing a Cucumber feature instead and leaving it at
that.

To share, one project I’m on has had scenarios provided by the
customer and it is an app with a lot of business rules and the system
contains many components (not a typical Rails CRUD app). The scenarios
themselves focus on high level behaviour. We’ve used view specs to
drive out the views, and we’ve only placed the minimal code in the
steps to use the system to prove out the behaviour. Relying solely on
scenarios and step definitions did not work well for the short time it
was tried.

On another project that is entirely different the scenarios themselves
actually fleshed out the requirements of the UI. But the type of app
is vastly different than the first and it just made sense.

While I don’t agree that 99% is a rule of thumb I can see where
different apps have different needs, and some will naturally drive out
more of the UI from the scenarios, whereas others will have details to
the UI which may not be the driving force of the feature and its
scenarios, but they need to exist.

I know that style comes into play at some point to. There are some
folks who are cool with putting all view stuff in step definitions and
there are folks who prefer to have minimally sized step definitions
with the details in specs. I can’t say who’s right or wrong, but I
know where my comfort level is.

Matt, can you say where you gauge your app?

  • Are there many little scenarios covering the details of the page?
  • Are the scenarios comprised of many steps which look at all aspects
    of the page?
  • Are the step definitions detail oriented? e.g.: looking at the
    project looks at all of the pieces of information it should be
    displaying

I’m really interested in this topic as I think I’ve seen when Cucumber
is relied on too much and I’ve definitely seen where it’s been relied
on too little. So naturally I’m interested to find out more about
people’s projects to help gauge where things fall on the continuum,


Zach D.
http://www.continuousthinking.com
http://www.mutuallyhuman.com


#4

Hi,

Oops, forgot that.

OK, so I added:

Require ‘webrat/core/matchers/have_selector’

On the first line, and directly within the class:

Include Webrat::Matchers

Is this correct?

OK, so now all 16 examples pass. But one of them should be failing –
the
one with the name. Do you see any reason that is still passing?

Again my matches? Method is as follows:

def matches? response
  response.should have_selector('form#%s' % [@id]) do |form|
    !@block or @block.call form
  end
end

As a P.S. I hate to take the wind out of your sails here, but I was
reflecting only today how, after 9 months of using RSpec to TDD our
Rails app, we have massaged the view specs down to almost nothing. IMO
99% of the time you should be writing a Cucumber feature instead and
leaving it at that.

Yeah, I’m really confused about that. I thought I’ve read from some
people
that they recommend doing Cucumber, but also view specs, and controller
and
model specs to test the different layers. Others say they don’t test the
view with specs, so I don’t know. I’m doing it for now and see how I
feel
about it.

Thanks,
Brandon


#5

On 2 Apr 2009, at 22:20, Brandon O. wrote:

#have_selector is part of webrat. Have you required the appropriate

Include Webrat::Matchers

Is this correct?

Think so. Did the error go away?

end
I don’t think you want to be using #should here. If that fails it will
raise an exception but the expected behaviour of a matcher is to
return true / false from #matches? so that will be one problem you
have. Try just hard-coding the #matches? method to return false and
see if you get one of your tests to fail.

What are you imagining that #response is going to return in this
instance?

Matt W.
http://beta.songkick.com
http://blog.mattwynne.net


#6

Hi again,

Well, I looked in the Webrat matchers code, and saw they also have a
block
argument to the matches? Method, though I don’t know why. So I copied
what
they did there, and got a bit more informative output. Unfortunately now
I
have more errors.

Before I continue, do you know why I have to include &block as the
second
argument to `matches?’?

So now it won’t recognize my subject field. The error is something like
this:

‘/contact/index the contact form before it has been submitted the
subject
dropdown box should have a feedback option’ FAILED
expected following output to contain a <select[id=subject]/> tag:

Subject Feedback Questions Suggestions Other
./spec/views/contact/index.html.erb_spec.rb:39: /home/Brandon/projects/bartenders-to-go/spec/views/matchers/form_matchers/ha ve_a_form_with_id.rb:16:in `call' /home/Brandon/projects/bartenders-to-go/spec/views/matchers/form_matchers/ha ve_a_form_with_id.rb:16:in `matches?' /home/Brandon/projects/bartenders-to-go/vendor/gems/webrat-0.4.3/lib/webrat/ core/matchers/have_xpath.rb:21:in `call' /home/Brandon/projects/bartenders-to-go/vendor/gems/webrat-0.4.3/lib/webrat/ core/matchers/have_xpath.rb:21:in `matches?' /home/Brandon/projects/bartenders-to-go/spec/views/matchers/form_matchers/ha ve_a_form_with_id.rb:15:in `matches?' ./spec/views/contact/index.html.erb_spec.rb:38:

That example is something like:

    it "should have a feedback option" do
      response.should have_a_contact_form do |form|
        form.should have_selector('select', :id => 'subject') do

|subject|
subject.should have_selector(‘option’, :content =>
‘Feedback’)
end
end
end # it “should have a feedback option”

Thanks for any help.

Brandon


#7

return true / false from #matches? so that will be one problem you
have. Try just hard-coding the #matches? method to return false and
see if you get one of your tests to fail.

What are you imagining that #response is going to return in this
instance?

Oh OK. Well what I want is to be able to test if that form exists on the
page.

So how would you recommend going about it otherwise? I tried putting a
begin
… end block around it, but that doesn’t seem to work either.

def matches? response, &block
  @block ||= block
  begin
    response.should have_selector('form#%s' % [@id]) do |form|
      !@block or @block.call form
    end
  rescue
    false
  else
    true
  end
end

It still has an error, but now it’s covered up by my failure_message. I
output $! As a test, though, and it’s the same error.

Brandon


#8

On 2 Apr 2009, at 22:21, Zach D. wrote:

on too little. So naturally I’m interested to find out more about
people’s projects to help gauge where things fall on the continuum,

What we have are a relatively small number of pages that have a lot of
different stuff on them, and that stuff depends a lot on the context.

So for example, a Concert page will show media about that concert, but
we juggle the media around depending on what there happens to be -
we’ll prefer to show a poster if someone has uploaded one, but
otherwise we’ll show a photo if there’s one of those. Then, depending
on the aspect (landscape / portrait) of the photo, we’ll use a
different grid layout for the media and show different numbers of them.

All these little rules get complex pretty quickly, and there are two
advantages I’ve found for expressing them in Cucumber.
(1) They’re super easy to read for both the CSS / markup guy who
fiddles with the layout, the product owner, and anyone else who wants
to get a handle on them
(2) They’re totally de-coupled from the implementation, which means I
can start with some ugly thing that filters and juggles arrays, while
we figure out exactly how we want it to work, then later optimize it
into a database query once the requirements have solidified.

This is the key thing for me about driving everything out from
acceptance tests - you get absolute flexibility about your
implementation with the confidence to know you can change whatever you
like and you’re still covered. The trade-off I guess is the length of
the build, and possibly the maintainability of the test suite if
people get sloppy about keeping it tidy. The first is definitely an
issue for us now, but the second is not really a problem at all for us
as yet - 910 scenarios and counting…! :slight_smile:

So to answer your questions, we would parts of the behaviour of each
page on the site as different features - so I guess this maps to what
you call ‘many little scenarios covering the details of the page’

If you email me directly I’ll send you a beta invite and you can take
a look for yourself :slight_smile:

Matt W.
http://beta.songkick.com
http://blog.mattwynne.net


#9

Hi,

I’ve still been working on this, and can’t figure it out.

I did see this old thread that covers a similar problem down the page a
bit:
http://groups.google.com/group/rspec/browse_thread/thread/b0c08ffd06c0f9e6#m
sg_25d4f7ccf7362853

The OP eventually links to a project where it explains how to do what he
was
trying to do, which is here:
http://github.com/yura/howto-rspec-custom-matchers/tree/master

So I looked through, and it seems promising, but it’s still not working.

I mean that the difference is that he is doing something like:

response.should have_form(’/users’) do
with_text_field ‘First name’, ‘user_first_name’
end

Etc. But I want to pass a variable to the block with the scope, so I
want to
do something like:

response.should have_form(’/users’) do |form|
form.should have_text_field ‘First name’
end

I find that more readable.

So does anyone know how I can do this?

Also, in this person’s code, he passes a scope to the matcher, like:

Def have_form(action, &block)
HaveForm.new action, self, &block
End

And then he uses it in matches? Like:

response.should @scope.have_tag(‘form[action=?]’, @action) do |form|
@block.call if @block
end

So I’m stumped. Any help would be much appreciated, as I’ve been working
on
this for at least three days now.

Thanks,
Brandon