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:
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.
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
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
Building a Rails Engine