‹prev |
My Chain |
next›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)
template = File.read("./#{template_path.sub(/^\//, '')}")
engine = Haml::Engine.new(template)
@response = engine.render(Object.new, assigns_for_template)
end
(
commit)
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" do
render("views/recipe.haml")
response.should have_selector(".preparations") do |preparations|
prepartions.
should have_selector(".ingredient > .name", :content => 'egg')
end
end
Running this spec fails because the needed HTML document structure is not in place:
strom@jaynestown:~/repos/eee-code$ ruby ./spec/views/recipe.haml_spec.rb
.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
Implementing that document structure in Haml is pretty darn easy:
%h1
= @recipe['title']
%ul.preparations
%li.ingredient
%span.name
egg
Running the spec now passes:
cstrom@jaynestown:~/repos/eee-code$ ruby ./spec/views/recipe.haml_spec.rb
..
Finished in 0.01163 seconds
2 examples, 0 failures
A 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
@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:
{
"quantity": 10,
"ingredient": {
"name": "spinach",
"kind": "frozen"
},
"unit": "ounces",
"description": "cooked in microwave"
}That level of complexity is not needed to specify that an ingredient name exists. Instead use an egg preparation of:
@recipe['preparations'] =
[ 'quantity' => 1, 'ingredient' => { 'name' => 'egg' } ]
Updating the Haml template to display a recipe's ingredients is easy:
%h1
= @recipe['title']
%ul.preparations
- @recipe['preparations'].each do |preparation|
%li.ingredient
%span.name
= preparation['ingredient']['name']
(
commit)
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.rb
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
Ah, there are no preparation instructions in the first example. When a recipe includes no preparation / ingredients, nothing should be displayed:
context "no ingredient preparations" do
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
I like the use of
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:
%h1
= @recipe['title']
- if @recipe['preparations']
%ul.preparations
- @recipe['preparations'].each do |preparation|
%li.ingredient
%span.name
= preparation['ingredient']['name']
Making this spec page also resolves the problem in the original spec:
cstrom@jaynestown:~/repos/eee-code$ ruby ./spec/views/recipe.haml_spec.rb -cfs
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
It 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.
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)