Posts Tagged TDD

Spec your Cells with rspec-cells

Saturday, December 11th, 2010

Today we released rspec-cells 0.0.1 which brings #render_cell to your cell examples.

While we ship Cells with a test case for Test::Unit the RSpec example group for cells has been missing for a long time. Based on Dmytro Shteflyuk’s rspec-cells for RSpec1 we now got support for RSpec2.

Usage

Put the following code in your Rails app’s Gemfile.

group :test do
  gem 'rspec-cells'
end

Just drop your examples into spec/cells/. Here’s how a cell spec could look like.

describe PostsCell do
  render_views
 
  it "should render the posts count" do
    render_cell(:posts, :count).should 
      have_selector("p", :content => "4 posts!")
  end
end

So we basically got a #render_cell for specing which simply returns the rendered markup.

To run your examples we got a rake task.

$ rake spec:cells

Is there more?

Presently, we go without a distinction between controller-only and view-only specs as you might know from controller specs.

Views are always rendered in rspec-cells as in a real functional test.

This is still subject to discussion – if you feel like you could need an explicit way to test your cell class only, or to render your views without executing the state code, let us know.

However, keep in mind that cells should be independent components cutting monolithic controllers into small manageable pieces, and thus might not need to be tested on different layers.

Have fun specing your cells and tell us what you think.

Meta-Testing your Assertions

Sunday, October 17th, 2010

As gems get more complex it is good practice to ship additional assertion methods with your library, so users can easily test code they wrote using your library.

Usually you do this by providing either a module or some derived TestCase, in a Test::Unit or MiniTest environment.

Tests without special assertions

The world-famous are-u-cool gem checks persons – whether they’re cool or not by querying some awesome web service.

Applications using this gem would usually have some test like this.

class PersonsTest < Unit::TestCase
  it "is the opposite of cool" do
    assert_not AreUCool.new.cool?("Helder")
  end
end

Domain-specific tests

Since this is a lot of work, the gem ships with its own test case to help you.

class AreUCool::TestCase < Unit::TestCase
  def setup
    @instance = AreUCool.new
  end
 
  # Asserts +name+ is cool.
  def assert_cool(name)
    assert @instance.cool?(name)
  end
end

Simplifying your application tests, an example usage would naturally look like the following.

class PersonsTest < AreUCool::TestCase
  it "is extremely cool" do
    assert_cool "Nick"
  end
end

See the difference?

The new test case makes it easy to test coolness.

Testing your test

Many gems do this. They provide modules or test classes to make your life easier.

However, mostly these test extensions aren’t tested at all, or are tested wrong. The gem authors usually “test” their test methods like this (as seen in Rails and in my own tests).

# in are-u-cool/test/test_case_test.rb
class TestCaseTest < AreUCool::TestCase
  it "should respond to assert_cool" do
    assert_cool "Nick"
  end
end

The problem here is: The tested test tests itself, which is almost like this unit test, for instance.

class PersonsTest < Test::Unit
  it "should respond to #name" do
    Person.new.name
  end
end

This test just tests if a method can be run in general. The same happens in the second last example. We use the new assertion, nothing more.

Don’t mix meta levels

We confused two different layers here! So, keep these rules in mind when testing your tests:

  • Don’t derive your test case from that which you’re testing!
  • Double-assert your new assertions!

Testing a TestCase is relatively simple. Look at how the new TestCaseTest looks.

class TestCaseTest < Unit::TestCase
  setup do
    @test = AreUCool::TestCase.new(:burp)
    @test.setup
  end
 
  it "should respond to assert_cool" do
    assert @test.assert_cool("Nick")
  end

Notice how I use two asserts in one line? This cleanly tests if the new assertion returns true on a 100% valid assertion.

Being true pessimists, we double-check a failing assertion, too.

  it "should complain about uncool persons" do
    exc = assert_raises(MiniTest::Assertion) do
      @test.assert_cool("Helder")
    end
 
    assert_equal "Expected true.", exc.message
  end
end

We simply catch the Assertion exception that is thrown if the assert_cool assertion fails.

Now, how would you do that in a self-contained test? You wouldn’t, right.

Following this, you should never use a test to test itself, but run tests against an instance of it – cleanly separating meta levels.

Testing your Rails 3 Engine sitting in a Gem

Monday, October 11th, 2010

or Making your Rails gem suck less

When people refer to gems they usually think of new methods and classes popping up in your Ruby application as soon as you require the gem.

However, with Rails 3, gems (and plugins) can also add behaviour, controllers, views, routes and all the other well-known pieces of Rails to your host application. These kind of gems are called engines.

Luckily, in Rails 3 we got baked-in engines support. When adding the gem to your host application, Rails will detect the engine and automatically extend things for you.

What can Engines do for me?

I, being a components-facist who refuses to write monolithic apps, am very happy about engines: They help you encapsulating parts of your application and encourage reusability in and between other projects.

Man, WordPress keeps telling me to update.

Having read great posts about writing engines, I will focus on testing engines in this post.

Testing engines right

So, if you use my Apotomo gem (and you should do that if you want real web components in your Rails app) it will add

  • new classes and stuff
  • new methods to controllers
  • add a new route

Now how do I test all that in the gem?

The Rakefile

First, I setup a Rakefile containing a Rake::TestTask. It sits in the gem’s top directory.

Rake::TestTask.new(:test) do |t|
  t.libs << 'test'
  t.test_files = FileList['test/**/*_test.rb']
  t.verbose = true
end

I can now run rake test in my gem directory which will launch my gem test suite.

The test helper

Every test suite needs a file to require all needed stuff. Usually you put that into test/test_helper.rb and require it in every test.

require 'rubygems'
require 'bundler'
Bundler.setup
 
require 'shoulda'
require 'apotomo'

I use bundler when running my tests, which makes loading local gems for development simple.

Unit tests

Testing classes and methods in gems is simple – the same setup as in your app. A unit test could look like this.

require 'test_helper'
 
class ApotomoTest < Test::Unit::TestCase
  context "The main module" do    
    should "respond to js_framework" do
      Apotomo.js_framework = :jquery
      assert_equal :jquery,  Apotomo.js_generator
    end
  end
end

The test helper is required, then we run an ordinary unit test against the gem classes, modules and methods.

Testing controllers

Things get complicated when it comes to testing controllers that use new Apotomo behaviour. I don’t want to setup things, so I use a small Rails test application in my tests. I initially stole that from José Valim’s devise, that everybody loves.

Enginex is a generator

The enginex command creates a rails app for me.

apotomo/test$ enginex dummy

Unfortunately, enginex assumes I want a new gem with a setup test directory, so what it does is:

dummy
|-- Gemfile
|-- lib
|   `-- dummy.rb
|-- Rakefile
`-- test
    |-- dummy
    |   |-- app
    |   |   |-- controllers
    |   |   |   `-- application_controller.rb
    |   |   `-- views
    |   |-- config
    |   |   |-- application.rb
    |   |   |-- environment.rb
    |   |   `-- routes.rb
  ...

What I need resides in dummy/test/dummy, so I move things a bit.

apotomo/test$ mv dummy dummy_gem
apotomo/test$ mv dummy_gem/test/dummy dummy
apotomo/test$ rm -r dummy_gem

Now I got a rails app in my test/ directory.

dummy/
|-- app
|   |-- controllers
|-- config
|   |-- application.rb
|   |-- environment.rb
...

I also remove the line

require "dummy"

in test/dummy/config/application.rb.

You’re now free to setup testing scenarios in dummy by adding controllers, actions and everything else.

Requiring the test app

My apotomo/test/test_helper.rb gets a bit longer.

ENV['RAILS_ENV'] = 'test'
require "dummy/config/environment"
require "rails/test_help"

This is all I need for now, I can now run a ActionController::TestCase against a controller in my dummy app.

Note that you should first require your engine, then the rails app so your tested gem is available in the dummy app.

Functional tests

Let’s go actually testing controllers.

Remember: we’re testing a gem that extends controllers, so first, we write a small imaginary controller and put it in test/dummy/app/controllers/dummy_controller.rb.

class DummyController < ApplicationController
  include Apotomo
 
  def apotomo
    render_widget "root" # Just some bullshit to test Apotomo.
  end
end

In the controller, I really use Apotomo as if I would be a user using it. No monkey-patching and stuff – it is a test validating that my gem works in shitty real-world controllers.

require 'test_helper'
 
class ApotomoIntegrationTest < ActionController::TestCase
  tests DummyController
 
  context "The controller" do
    should "render widgets" do
      get :apotomo
      assert_select "div#root", "I'm root"
    end
  end
end

ActionController::TestCase provides a relatively easy way to write functional tests.

Naturally, you can also test “native” controllers and routes from your engine the same way.

Unit testing controllers

Sometimes pure functional tests are too less and a unit test for your controller methods would be handy – I do that often.

require 'test_helper'
 
class ApotomoControllerMethodTest < ActionController::TestCase
  tests DummyController
 
  context "The controller" do
    should "respond to #render_widget" do
      assert_responds_to @controller, :render_widget
    end
  end
end

The test case automatically provides a controller instance in @controller. You’re free to invoke methods and test ‘em.

These were the basic steps, if you wanna know more, ask!

Comments appreciated

Dudes, why are you guys so shy? Tell me how you do it, what you like and what sucks. I really appreciate comments.

Getting a bloody Rails 3 generator running. And testing it.

Wednesday, September 15th, 2010

Suck me sideways! It is said the generator layer in Rails 3 got really ohsom, flexible and whatever. I can now confirm “That’s true, somehow.” – nevertheless it took me years to figure out all the internals, conventions and places where to find documentation.

What I wanted

So here’s my requirement. I have a gem cells. It’s ready for Rails 3. It’s even mentioned in the Rails’ weblog! Anyway, it needs a generator to create assets for the users. In Rails 2.3 running something like

rails2 $ script/generate cell blog latest --haml

would generate several files in app/cells/ inside the rails application.

So, basically, I need a generator inside a gem to create… files. Nothing more.

Where I put it

Here’s how I ended up with my generator. It resides at
cells/lib/generators/cells/cell_generator.rb.

Two things noteworthy here.

  • Put your generators in your gem’s lib/generators directory, nowhere else.
  • The last directory containing the generator class had to be named cells (like the gem). Any other filename worked fine when running rails g, which would list my generator.
    However, when trying to generate with rails g cells:cell it failed with “Could not find generator cells:cell.“.

I was able to find some informations on naming in the generators guide. I don’t really like the example there, and I had to dive into the code to figure out things, anyway.

How I did it

Here’s how the generator looks like (I stole a bit from devise).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
require 'rails/generators'
require 'rails/generators/named_base'
 
module Cells
  module Generators
    class CellGenerator < ::Rails::Generators::NamedBase
      argument :actions, :type => :array, :default => [], :banner => "action action"
      check_class_collision :suffix => "Cell"
 
      source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
 
      class_option :view_engine, :type => :string, :aliases => "-t", :desc => "Template engine for the views. Available options are 'erb' and 'haml'.", :default => "erb"
      class_option :haml, :type => :boolean, :default => false
 
 
      def create_cell_file
        template 'cell.rb', File.join('app/cells', class_path, "#{file_name}_cell.rb")
      end
 
      def create_views
        if options[:view_engine].to_s == "haml" or options[:haml]
          create_views_for(:haml)
        else
          create_views_for(:erb)
        end
      end
 
      def create_test
        @states = actions
        template 'cell_test.rb', File.join('test/cells/', "#{file_name}_cell_test.rb")
      end
 
    protected
 
      def create_views_for(engine)
        for state in actions do
          @state  = state
          @path   = File.join('app/cells', file_name, "#{state}.html.#{engine}")
 
          template "view.#{engine}", @path
        end
      end
    end    
  end
end

Let’s discuss some basics.

  • Derive your generator from Rails::Generators::NamedBase if it expects at least one argument (line 6)
  • Use Generator.source_root to define where your templates are located (line 10)
  • Every public method is invoked automatically during the generation process. That’s something you should know. I didn’t know that.
  • Be sure to read the Thor manual, as this is the base for Rails’ generators. E.g. Generator.template will parse the specified ERB view and drop it in the passed new location (line 17).
  • Options from the command line can be retrieved from the Generator.options method (line 21).

The complete generator files with its views and shit can be found at my repository.

Testing it

I hate manual testing. Instead of calling

rails g cells:cell blog latest --haml

a thousand times I wrote generator tests. No docs on that could be found, so far.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require File.join(File.dirname(__FILE__), 'test_helper')
 
require 'generators/cells/cell_generator'
 
class CellGeneratorTest < Rails::Generators::TestCase
  destination File.join(Rails.root, "tmp")
  setup :prepare_destination
  tests ::Cells::Generators::CellGenerator
 
  test "create the standard assets" do
    run_generator %w(Blog post latest)
 
    assert_file "app/cells/blog_cell.rb", /class BlogCell < Cell::Rails/
    assert_file "app/cells/blog_cell.rb", /def post/
    assert_file "app/cells/blog_cell.rb", /def latest/
    assert_file "app/cells/blog/post.html.erb", %r(app/cells/blog/post.html.erb)
    assert_file "app/cells/blog/latest.html.erb", %r(app/cells/blog/latest.html.erb)
    assert_file "test/cells/blog_cell_test.rb"
  end

Generator tests are super smooth, just watch out for the following.

  • Be sure to require your generator (line 3).
  • Derive your test from the nice Rails::Generators::TestCase (line 5).
  • With TestCase.run_generator you can simulate a command line invocation of your generator and pass arbitrary arguments (line 11).
  • The handy TestCase.assert_file method checks if the file was generated. Additionally, you can pass a regex as second argument which is matched against the file content.

Props to the very intuitive and easy Generators::TestCase API, I do appreciate that.

Reflecting the past decades

At first, I was overwhelmed by all the features like hooks and namespaces. Now that I understand the principles of Rails 3 generators it almost seems ridiculously easy. And, if you don’t understand things, always remember to peek at the Thor docs. Maybe the generator guide should mention that somewhere.

Peace.