RSpecsters:
I like nested XPath expressions, because the outer XPath assertion can
clip the
diagnostic of the inner XPath assertion. You don’t get the whole page
spewed
into your face when you fault!
This specification, for example, tests Yury K.'s user login page,
from:
http://github.com/yura/howto-rspec-custom-matchers/tree/master
it ‘should have xpathic tags’ do
render ‘/users/new’
response.body.should be_xml_with{
xpath :form, :action => '/users' do
xpath :fieldset do
xpath :'legend[ contains(., "Personal Information") ]' and
xpath :'label[ contains(., "First name") ]' and
xpath :input, :type => 'text', :name => 'user[first_name]'
end
end
}
end
That tests this (otherwise innocuous) new.html.erb:
Personal Information- First name
If that code had any major complex <%= erb %> activity, the tests^W
specifications would keep it honest.
Let’s change the specification, to simulate a bug, and try it:
xpath :input, :type => 'text', :name => 'user[first_nome]' # was
_name
That provides this mind-blast of errata:
‘/users/new should have xpathic tags’ FAILED
xpath: “descendant-or-self::input[@type = $type and @name = $name]”
arguments: {“name”=>“user[first_nome]”, “type”=>“text”}
xml context:
Personal Information- First name
assert{ ( ( xpath(:“legend[ contains(., "Personal Information") ]”) )
and
( ( ( xpath(:“label[ contains(., "First name") ]”) ) and
( xpath(:input, { :type => “text”, :name => “user[first_nome]” }) )
) ) ) }
→ nil - should pass
xpath(:“legend[ contains(., "Personal Information") ]”)
→ … </>
xpath(:“label[ contains(., "First name") ]”)
→ … </>
xpath(:input, { :type => “text”, :name => “user[first_nome]” })
→ nil
./spec/views/users/new.html.erb_spec.rb:63:
script/spec:5:
Finished in 0.116823 seconds
2 examples, 1 failure
Note that the error message restricted itself to the XHTML inside the
tag. This is a major benefit when diagnosing a huge page that failed. (But also note that your HTML, like your code, should come in small reusable snippets, such as partials, and that these should get tested directly!)Soon I will upgrade this system to use Nokogiri instead of (>cough<)
REXML.
Now, while I go and put the “simple” matcher that does this onto
Twitter,
YouTube, Mingle, Facebook, Mindfuck, Reddit, Tumblog, LinkedIn, and
Gist, you
all can just read it below my sig.
–
Phlip
http://www.zeroplayer.com/
require File.dirname(FILE) + “/…/…/spec_helper”
require ‘assert2/xpath’
require ‘spec/matchers/wrap_expectation’
Spec::Runner.configure do |c|
c.include Test::Unit::Assertions
end # TODO blog this
describe “/users/new” do
it “should have user form” do
render ‘/users/new’
response.should have_form(‘/users’) do
with_field_set ‘Personal Information’ do
with_text_field ‘First name’, ‘user’, ‘first_name’
end
end
end
class BeXmlWith
def initialize(scope, &block)
@scope, @block = scope, block
end
def matches?(stwing, &block)
waz_xdoc = @xdoc
@scope.wrap_expectation self do
@scope.assert_xhtml stwing
return (block || @block || proc{}).call
end
ensure
@xdoc = waz_xdoc
end
attr_accessor :failure_message
def negative_failure_message
"yack yack yack"
end
end
def be_xml_with(&block)
BeXmlWith.new(self, &block)
end
def be_xml_with_(&block)
waz_xdoc = @xdoc
simple_matcher ‘yo’ do |given, matcher|
wrap_expectation matcher do
assert_xhtml given # this works
block.call # crashes with a nil.first error!
end
end
ensure
@xdoc = waz_xdoc
end
it ‘should have xpathic tags’ do
render ‘/users/new’
response.body.should be_xml_with{
xpath :form, :action => '/users' do
xpath :fieldset do
xpath :'legend[ contains(., "Personal Information") ]' and
xpath :'label[ contains(., "First name") ]' and
xpath :input, :type => 'text', :name => 'user[first_name]'
end
end
}
end
end