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?
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.
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.
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
dummy/ |-- app | |-- controllers |-- config | |-- application.rb | |-- environment.rb ...
I also remove the line
You’re now free to setup testing scenarios in
dummy by adding controllers, actions and everything else.
Requiring the test app
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.
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
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!
Dudes, why are you guys so shy? Tell me how you do it, what you like and what sucks. I really appreciate comments.