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
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.