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.
Hey Nick, thanks for the post.
Seems like I’ve been following the same path as you, but my engine is more to ready component than to plugin.
What I do now, is just making the engine a full-fledged rails app. Actually, that’s not new, DHH in his Tolk does it too.
I’ve added config/{application.rb,boot.rb,database.yml,environment.rb}, and bootstraped it all with rspec:install rake task.
As the engine is mountable component, tests without dummy application a la enginex seem more natural to me. Tell what you think, maybe there are rough cases I didn’t stumble upon yet?
So far, I have problems only with engine namespacing, but that’s mainly to file structure (app/{controllers,views}/namespace/ etc).
LikeLike
Me too on this rails engine band-wagon. Here is my 3 part series on creating rails 3 engine with the dummy app installed via enginex –
http://nepalonrails.com/blog/2010/09/Creating-a-Rails-engine-with-tests-and-a-dummy-rails-application-embedded-in-it/
LikeLike
Hi Nick.
Thanks for your post. I’ve planned to add tests for my engine long ago, now I suppose it will be less pain.
I have a question regarding deploying your engine-gem to passenger. Haven’t you get troubles with dependencies? I do. On my local environment I simply include required gems in my files inside lib.
But when I deploy that on nginx+passenger server, run `bundle isntall` and everything needed, passenger display error that he couldn’t find that library. I tried
require ‘rubygems’ but it not helps at all.
LikeLike
@rofh: So, you basically execute “rake test” in the gem directory?
This sounds good – is this a Rspec-only thing, or can you test your mountable engine with Runit, too?
@Vladimir: Does passenger complain about the missing engine gem itself?
LikeLike
@nick: no, engine is working fine. But problem comes when I try to require some gems in my files
LikeLike
@nick: nope, this isn’t rspec specific. actually, if you don’t need early rails initialization in engine, you can just generate new default rails app and place it to vendor/plugins, then change its “AppName::Application” to “Rails.application” and you’re ready to go (don’t forget to copy migrations to the home app as you develop).
LikeLike
I was thinking of using enginex in the same way you did here. Thank you for sharing 🙂
LikeLike
hi,
one think we did was to copy the schema.rb from the main application across into the rails dummy app. it was no problem calling rake db:test:prepare to install the tables into sqlite.
background is that we started with our gems in our main application and are now refactoring these gems out into a separate gem. this means that there is a certain amount of stuff that these gems require in order to be standalone tested.
LikeLike
Another take on this theme http://freelancing-gods.com/posts/combustion_better_rails_engine_testing
LikeLike