Testing gems and plugins best practices in ruby on rails


Lack of a dedicated test suite in gems

The lack of a dedicated test suite in many Rails plugins and gems undermines their reliability and limits their integration into larger applications.

This inherent flaw inhibits community contributions and creates challenges as the parent application evolves.

The dependency on the originating application’s test suite makes the plugin less adaptable and more tied to a specific application lifecycle, potentially rendering test cases obsolete as updates occur.

Solutions

Write Normal Unit Tests Without Rails

Often, the best approach to testing Rails plugins or gems that minimally interact with Rails is to treat them as standalone Ruby modules. Consider this example of a simple plugin:

# 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

This plugin is easily tested in isolation because it doesn’t interact with Rails components:

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

Testing is straightforward as it avoids Rails dependencies, fostering a simpler testing environment that encourages contributions.

Load Only the Parts of Rails You Need

For plugins that interact with specific Rails components, it’s efficient to load only those necessary parts into your test environment instead of a full Rails stack:

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

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

Example unit test for the Slugalicious plugin, which interacts primarily with 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

This method is less resource-intensive and simplifies testing by isolating the plugin from the full Rails application environment.

Create a brand new rails app in your test folder

Embedding a full Rails application within a plugin’s test suite represents a complex approach that should be avoided unless absolutely necessary.

Let me repeat: should be avoided unless absolutely necessary.

Embed a Complete Rails Application in Your Tests

Some plugins may require interaction with the entire Rails stack including all components like a model, controller, routes, and views.

In such cases, a comprehensive approach involves embedding a simplified Rails application that incorporates the plugin.

The Grunt Work

To emulate a complete environment for testing, the following steps are necessary:

  1. Generate a base plugin skeleton and set up a testing environment:

    cd blawg/test
    rails new rails_root
    

    This command will scaffold a new Rails application, preparing a testing ground for the plugin.

  2. Configure the test environment to use 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 application using plugin path settings:

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

This setup ensures the test application can load and interact with the plugin seamlessly.

Running and Extending Tests

Once configured, developers can write standard unit tests and functional tests within this environment.

For example, testing a Post model might involve checking database columns and validating presence:

# 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

Strategies

Simple Plugin Testing

Isolation from Rails allows for unit testing of plugins in a straightforward manner.

By focusing on core Ruby testing practices, developers can guarantee that the functionality is preserved independently of the Rails application, ensuring the plugin’s usability across different projects.

More Integrated Plugin Testing

In cases where the plugin needs to interact with Rails components, selectively loading those components—like ActiveRecord or ActionPack—can provide a middle ground between full integration testing and isolated unit tests.

This approach maintains test clarity and focus while still ensuring that the plugin functions correctly within the Rails ecosystem, but without the overhead of a full Rails application stack.

The reality

If I could bet, I would say that 90% of Rails developers have never tried to code a gem. Which internally means that they know how to use Rails, but have never written Ruby.

The reality is that when you are maintaining gems, you are maintaining Ruby code.

In my experience, many developers struggle with this aspect.

Have you already created your own gem or plugin?


Related posts

Free Training By Email:
Building a Rails Engine

We will send udpates but we care about your data.
No spams.

Back to homepage