Cleanup for Cross-Project Consistency
Reading through the RSpec Beta book, I noticed that view specs are rendered without the initial slash (e.g.render("views/recipe.haml")
). To prevent confusion when switching between projects, I modify the my Sinatra spec helper to work with or without the initial slash:def render(template_path)(commit)
template = File.read("./#{template_path.sub(/^\//, '')}")
engine = Haml::Engine.new(template)
@response = engine.render(Object.new, assigns_for_template)
end
So now, it's back to view spec'ing...
Specifying the Ingredients Display
We already have the recipe title showing, next up is displaying ingredients. On the recipe page, there should be a section listing the ingredients, with simple preparation instructions (e.g. diced, minced, 1 cup measured, etc). This can be expressed in RSpec as:it "should render ingredient names" doRunning this spec fails because the needed HTML document structure is not in place:
render("views/recipe.haml")
response.should have_selector(".preparations") do |preparations|
prepartions.
should have_selector(".ingredient > .name", :content => 'egg')
end
end
strom@jaynestown:~/repos/eee-code$ ruby ./spec/views/recipe.haml_spec.rbImplementing that document structure in Haml is pretty darn easy:
.F
1)
'recipe.haml should render ingredient names' FAILED
expected following output to contain a <.preparations/> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><h1>
Recipe Title
</h1></body></html>
./spec/views/recipe.haml_spec.rb:27:
./spec/views/recipe.haml_spec.rb:3:
Finished in 0.010398 seconds
2 examples, 1 failure
%h1Running the spec now passes:
= @recipe['title']
%ul.preparations
%li.ingredient
%span.name
egg
cstrom@jaynestown:~/repos/eee-code$ ruby ./spec/views/recipe.haml_spec.rbA template that only includes a single ingredient of "egg" is not going to be all that useful. To get the ingredient name into the HTML output, we need to build up the
..
Finished in 0.01163 seconds
2 examples, 0 failures
@recipe
data structure in the example. The ingredient preparation data structures in our legacy system are somewhat complex. For example, 10 ounces of frozen spinach defrosted in the microwave is represented as:{That level of complexity is not needed to specify that an ingredient name exists. Instead use an egg preparation of:
"quantity": 10,
"ingredient": {
"name": "spinach",
"kind": "frozen"
},
"unit": "ounces",
"description": "cooked in microwave"
}
@recipe['preparations'] =Updating the Haml template to display a recipe's ingredients is easy:
[ 'quantity' => 1, 'ingredient' => { 'name' => 'egg' } ]
%h1(commit)
= @recipe['title']
%ul.preparations
- @recipe['preparations'].each do |preparation|
%li.ingredient
%span.name
= preparation['ingredient']['name']
Uncover a Bug? Don't Fix It... Write a Spec!
Running the whole spec, however, causes a failure in the first example!cstrom@jaynestown:~/repos/eee-code$ ruby ./spec/views/recipe.haml_spec.rbAh, there are no preparation instructions in the first example. When a recipe includes no preparation / ingredients, nothing should be displayed:
F.
1)
NoMethodError in 'recipe.haml should display the recipe's title'
undefined method `each' for nil:NilClass
(haml):5:in `render'
/home/cstrom/.gem/ruby/1.8/gems/haml-2.0.9/lib/haml/engine.rb:149:in `render'
/home/cstrom/.gem/ruby/1.8/gems/haml-2.0.9/lib/haml/engine.rb:149:in `instance_eval'
/home/cstrom/.gem/ruby/1.8/gems/haml-2.0.9/lib/haml/engine.rb:149:in `render'
/home/cstrom/repos/eee-code/spec/spec_helper.rb:20:in `render'
./spec/views/recipe.haml_spec.rb:11:
./spec/views/recipe.haml_spec.rb:3:
Finished in 0.011262 seconds
2 examples, 1 failure
context "no ingredient preparations" doI like the use of
before(:each) do
@recipe[:preparations] = nil
end
it "should not render an ingredient preparations" do
render("views/recipe.haml")
response.should_not have_selector(".preparations")
end
end
context
here. In describing the example above, I used the word "When", which suggests a specific context in which the spec runs. It also aids in readability. Even in an age in which monitors are 1600 pixels wide, the less horizontal scanning needed, the easier it is to read the core concept of the code / spec.I can make this pass with a conditional in the Haml template:
%h1Making this spec page also resolves the problem in the original spec:
= @recipe['title']
- if @recipe['preparations']
%ul.preparations
- @recipe['preparations'].each do |preparation|
%li.ingredient
%span.name
= preparation['ingredient']['name']
cstrom@jaynestown:~/repos/eee-code$ ruby ./spec/views/recipe.haml_spec.rb -cfsIt would have been a mistake to attempt to fix the first spec failure directly by adding the conditional to the Haml template. The first spec was meant to test something very specific, if very simple. It happened to uncover a boundary condition. Fixing the boundary condition issue in that spec would have left the boundary condition uncovered. It also would not have given me the opportunity to codify my thinking in resolving the issue.
recipe.haml
- should display the recipe's title
- should render ingredient names
recipe.haml a recipe with no ingredient preparations
- should not render an ingredient preparations
Finished in 0.015988 seconds
3 examples, 0 failures
As it is, I am in a much better place now. I still have my original, simple spec which may yet uncover other boundary condition defects. I also have a specification of how I handle this specific boundary condition.
(commit)
Good work on the continued documentation of your chain. You're more disciplined with it than I.
ReplyDeleteHave you seen Elementor? It probably wouldn't make your view specs any fewer lines overall (it might later, when you add more), but you could write things like:
@page.should have(1).preparation_ingredient.with_text('egg')
and
@page.should have(0).preparations
I've only used it myself in one small project so far but I enjoy the assigning of meaningful names to the selectors and then using those names to make specs that read really well.
If I have see Elementor, it has long since passed from my conscious. Looks very, very nice. I will have to investigate it in more detail...
ReplyDeleteThanks for cluing me in!