Testing your Rails 3 Engine sitting in a Gem

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

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'

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

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:

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

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

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"

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

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.


11 thoughts on “Testing your Rails 3 Engine sitting in a Gem

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


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


  3. @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?


  4. @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).


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


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s