RL ROLAND LOPEZ
// 4 min read

Testing Gems and Plugins

Why a gem needs its own tests

A lot of Rails plugins and gems lean on the host app’s test suite. That makes them fragile.

With no tests of its own, a gem is hard to contribute to, hard to trust, and tied to one app’s lifecycle. Update the parent app and the gem can quietly break.

A gem should prove it works on its own. Here are four ways to test one, from lightest to heaviest.

1. Plain Ruby, no Rails

If the gem barely touches Rails, test it as a standalone Ruby module.

# lib/lorem_ipsum.rb
module LoremIpsum
  TEXT = "Lorem ipsum dolor sit amet, consectetur " +
         "adipisicing elit, sed do eiusmod tempor " +
         "incididunt ut labore et dolore magna aliqua. " +
         "Ut enim ad minim veniam, quis nostrud " +
         "exercitation ullamco laboris nisi ut aliquip " +
         "ex ea commodo consequat. Duis aute irure " +
         "dolor in reprehenderit in voluptate velit " +
         "esse cillum dolore eu fugiat nulla pariatur. " +
         "Excepteur sint occaecat cupidatat non proident, " +
         "sunt in culpa qui officia deserunt mollit anim " +
         "id est laborum."
  def lorem_ipsum(words = nil)
    if words
      TEXT.split[0..words - 1].join(' ') + "."
    else
      TEXT
    end
  end
end

It tests in isolation because it never touches Rails:

# test/test_helper.rb
require 'rubygems'
require 'test/unit'
require 'shoulda'
require 'lorem_ipsum'

No Rails dependencies, a simple setup, easy for others to contribute to.

2. Load only the Rails parts you need

If the gem uses specific Rails components, load just those instead of the full stack:

# test/test_helper.rb
require 'rubygems'
require 'active_record'
require 'shoulda'
require 'slugalicious'

ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')

Here is a Slugalicious test that only needs ActiveRecord:

# test/slugalicious_test.rb
require 'test_helper'
class SlugaliciousTest < ActiveSupport::TestCase
  should validate_presence_of(:slug)
  should allow_value("example-slug").for(:slug)
end

Lighter than booting all of Rails, and it keeps the test focused on the one component you depend on.

3. Embed a full Rails app (only if you must)

Some plugins touch the entire stack: models, controllers, routes, and views. Then you embed a small Rails app inside the gem’s test suite.

Avoid this unless you truly need it.

The grunt work:

  1. Generate a base skeleton and a test app:

    cd blawg/test
    rails new rails_root
  2. Point it at SQLite:

    # blawg/test/rails_root/config/database.yml
    development:
      adapter: sqlite3
      database: db/development.sqlite3
    test:
      adapter: sqlite3
      database: db/test.sqlite3
  3. Link the plugin to the test app:

    # blawg/test/rails_root/config/environment.rb
    Rails::Initializer.run do |config|
      config.plugin_paths = ["#{Rails.root}/../../../"]
      config.plugins = [:blawg]
    end

Now the test app can load and exercise the plugin.

Running tests

Write normal unit and functional tests in that app. Testing a Post model might look like this:

# blawg/test/rails_root/test/unit/post_test.rb
require 'test_helper'
class PostTest < ActiveSupport::TestCase
  should have_db_column(:title)
  should have_db_column(:body)
  should validate_presence_of(:title)
  should validate_presence_of(:body)
end

The reality

If I had to bet, 90% of Rails developers have never tried to code a gem. They know how to use Rails, but have never really written Ruby.

And maintaining a gem is maintaining Ruby code. That is where a lot of devs struggle.

Have you shipped your own gem or plugin yet?

Roland Lopez
Written by
Roland Lopez

Technical founder & AI crack-head

Built by Agent Skynet See the agency