Last night, I was able to get HAML working together with Sinatra and CouchDB. Unfortunately, I got stuck trying to spec HAML views independently of the Sinatra application. I have grown accustomed to the cushy life afforded by rspec-on-rails and need to be able to do similar things with HAML.
The
assigns[]
and render
methods are nowhere to be found—so how do I spec the views? The answer is to instantiate a Haml::Engine
object and manually invoke its render
method:require File.expand_path(File.dirname(__FILE__) + '/../spec_helper' )The call to
require 'haml'
describe "recipe.haml" do
before(:each) do
@title = "Recipe Title"
@recipe = { 'title' => @title }
template = File.read("./views/recipe.haml")
@engine = Haml::Engine.new(template)
end
it "should display the recipe's title" do
response = @engine.render(Object.new, :@recipe => @recipe)
response.should have_selector("h1", :content => @title)
end
end
render
requires two arguments as documented in the Haml API. The first is the scope in which the the template is evaluated. It is useful if you want to bind an object whose methods can be evaluated by the HAML template. Since I have no need for this, I simply pass in a new, top-level Object instance (which is the default). The second argument to
render
is a hash assignment of local variables. In this case, I want the template to see an instance variable @recipe
and assign it the value of the @recipe
variable defined in the before(:each)
. To do this, pass in a single-record hash with a key of :@recipe
and value of the before(:each)
's @recipe
instance variable.With that in place, the spec passes:
cstrom@jaynestown:~/repos/eee-code$ ruby ./spec/views/recipe.haml_spec.rb(commit, commit)
.
Finished in 0.008689 seconds
1 example, 0 failures
It is working, but I would much prefer to keep the
Haml::Engine
overhead out of my view specs. In other words, I would like to have these looking more like normal view specs:require File.expand_path(File.dirname(__FILE__) + '/../spec_helper' )That turns out to be not nearly as difficult as I expected it would be. Here is the updated
describe "recipe.haml" do
before(:each) do
@title = "Recipe Title"
@recipe = { 'title' => @title }
assigns[:recipe] = @recipe
end
it "should display the recipe's title" do
render("/views/recipe.haml")
response.should have_selector("h1", :content => @title)
end
end
spec_helper.rb
that allows the above spec to pass (comments inline):ENV['RACK_ENV'] = 'test'I am fairly pleased with my poor-man's rspec-for-haml-views implementation—mostly in that it actually works. It is not as robust as it would need to be for a gem release—when you neglect to set the proper
require 'eee'
require 'spec'
require 'spec/interop/test'
require 'sinatra/test'
require 'webrat'
require 'haml'
Spec::Runner.configure do |config|
config.include Webrat::Matchers, :type => :views
end
# Renders the supplied template with Haml::Engine and assigns the
# @response instance variable
def render(template)
template = File.read(".#{template}")
engine = Haml::Engine.new(template)
@response = engine.render(Object.new, assigns_for_template)
end
# Convenience method to access the @response instance variable set in
# the render call
def response
@response
end
# Sets the local variables that will be accessible in the HAML
# template
def assigns
@assigns ||= { }
end
# Prepends the assigns keywords with an "@" so that they will be
# instance variables when the template is rendered.
def assigns_for_template
assigns.inject({}) do |memo, kv|
memo["@#{kv[0].to_s}".to_sym] = kv[1]
memo
end
end
assigns
, you tend to get errors along the lines of:undefined method `[]' for nil:NilClassStill, it is good enough for now. More importantly it gave me a chance to explore how these things are typically implemented—good knowledge to have in my toolbelt.
(haml):2:in `render'
(commit)
Thanks for this, it was very helpful. I'm just starting with tests on rails and I wanted to write some tests for Sinatra helpers and HAML and I found your post very useful!
ReplyDelete