GemSuit
Test the entire usage workflow (including the generators) of your newly generated or existing gem within Rails 2 and 3 and make the gem development mobile.
As Barney Stinson advises: “Gem suit up! It’s gonna be legend” ^^
Introduction
We Rails developers are experiencing great evolvements at the moment: not only are we migrating from Ruby 1.8 to Ruby 1.9, but we are also migrating from Rails 2.3 to Rails 3. This means that Rails gem writers have to deal with this. Luckily, there is Rich-Support which provides Rails 2 and 3 compliance to your Rails gem, but you also want to be able to run integration tests within a Rails 2 and 3 environment.
And so this is where GemSuit kicks in: it provides an extensive test suite to a (newly generated or existing) gem in which you can test the whole gem usage process. From a clean Rails (2 or 3) application, to running your gem generators, to testing your front-end within Firefox with Capybara.
GemSuit is TestUnit, Shoulda (mainly for contexts), Capybara and Selenium driven and it also provides you to run the Rails (2 or 3) server and console (for development and testing purposes) very easily!
So what can I test exactly?
As already mentioned, GemSuit really aims on testing the entire process of using your gem in a Rails app. A GemSuit integration test can consist of the following steps:
- Start with a clean Rails (2 or 3) application (as if you just ran
rails new your_rails_app
) - Prep certain files (e.g. initializers) using Thor’s template generation capabilities
- Run several generators provided by your gem
- Load fixtures
- Open the Rails app in the browser and run assertions (with Capybara in Firefox)
And also you can run unit tests (and the other standard tests) within both Rails 2 and 3.
A real world example of a GemSuit implementation is Rich-CMS.
Quickstart
Run the following in your terminal:
$ gem install gem_suit $ suit tailor your_new_gem $ cd your_new_gem $ suit -v
Note: Make sure you have Firefox installed.
Installation
Run the following command in your console:
gem install gem_suit
Provide your gem with GemSuit
Generating a new gem
Run the following command to create a gem with Bundler and the GemSuit test suite:
suit tailor your_gem_name
Note: What suit tailor
actually does, is: bundle gem your_gem_name
, cd your_gem_name
, suit up
and finally suit fit
.
For an already created gem
Run the following command within your gem directory (e.g. in Gems/rich_cms
for the gem Rich-CMS)
suit up
Your gem is now provided with GemSuit ^^
Great. But now what?
Developing your gem
After you have made your gem a little bit more legend, you can start developing your gem with the Rails 2 and 3 dummy applications provided (within suit/rails-{2,3}/dummy
). This makes the gem development very mobile as you can commit them (with the Sqlite databases) in your Git repository! Just run the following to start the Rails 3 server:
suit s
Run the Rails 2 server on port 3001
as follows:
suit s -r2 -p3001
You can also start the Rails console (Rails 3 at default) of the dummy applications (use -r2
to use the Rails 2 console):
suit c
Making your gem development mobile
It is possible that you will have to develop your gem on another computer or that there are multiple developers (and thus coping with different computers). As mentioned in the previous section, everything is added to the Git repository. The only pitfall can be that the computer is not prepped for the gem development. Think of not having all the gem dependecies installed.
Just check out the gem Git repository, install GemSuit and “fit the GemSuit” with suit fit
. With Rich-CMS as example, run the following:
$ git clone https://github.com/archan937/rich_cms.git $ cd rich_cms $ gem install gem_suit && suit fit -v
Testing with GemSuit
Running the GemSuit integration tests (with Capybara and Selenium)
To run the integration tests just run the following:
suit -v
Note: The -v
option outputs a summary of the test results. You can also run the tests with just suit
for no additional output or suit -w
for very verbose output.
Restoring the Rails dummy applications
GemSuit uses the dummy applications for the integration tests by stashing and restoring its source files. When a test fails, the state of the Rails application directory stays as it is during the test. To reset the dummy application, just run the following command:
suit restore
Running unit (and the common) tests with Rails 2 and 3
The standard Rails provided tests can be run with:
suit test unit
To only run tests in Rails 3, use the following:
suit test unit -r3
Writing GemSuit integration tests
A real world example
Please explore https://github.com/archan937/rich_cms/tree/edge/suit for examples of the GemSuit integration test possibilities.
The provided ExampleTest
After running suit up
(or suit tailor
when creating a new gem), you will have suit/shared/test/integration/suit/example.rb
at your disposal. As you might have guessed, this is an example of a GemSuit integration test. You can see it in action by running:
suit -v
Essential sources and directories
Certain sources (and directories) are essential when writing a GemSuit integration test:
suit/shared/test/integration/suit/*.rb
This directory contains all the GemSuit integration tests.
At default it is provided with example.rb
, an example
suit/shared/test/suit_application/**/*.*
Source files within this directory will be required when running tests.
You are supposed to put additional lib
sources for testing purposes in here: an example
suit/shared/test/suit_application/capybara_extensions.rb
This source file contains helper methods when running the GemSuit integration test with Capybara.
Think of logging in, logging out and filling in a form: an example
suit/shared/test/suit_application.rb
The role of SuitApplication
is to prepare and restore the dummy application when running a GemSuit integration test.
Think of calling Rails generators, generating files with Thor and providing variables when compiling templates: an example
suit/shared/test/templates/**/*.*
The templates used by SuitApplication (and thus Thor) when generating files for test preparations.
You can write shared / Rails 2 specific / Rails 3 specific templates, an example
A GemSuit integration test
It is actually a simple ActionController::IntegrationTest
which requires suit_application.rb
instead of the regular test_helper.rb
.
Also, two methods of SuitApplication
are called:
-
SuitApplication.test
– Prepare the Rails dummy application for a certain environment (you can pass options toSuitApplication
) -
SuitApplication.restore_all
– Restore the Rails dummy application source files after running the integration test
The basic structure:
require File.expand_path("../../../suit_application.rb", __FILE__) SuitApplication.test :some => :variable class YourGemSuitIntegrationTest < GemSuit::IntegrationTest context "My example test" do setup do # prepare something end teardown do SuitApplication.restore_all end should "pass" do # visit some pages # click some links and fill in some forms # assert some statements end end end
SuitApplication class
This class prepares and restores the Rails dummy application when running a GemSuit integration test. You can leave it as is, but that wouldn’t be fun.
Note: See https://github.com/archan937/rich_cms/blob/edge/suit/shared/test/suit_application.rb for a real world example.
The following examples are combination with this GemSuit integration test:
require File.expand_path("../../../suit_application.rb", __FILE__) SuitApplication.test :authentication => :devise class YourGemSuitIntegrationTest < GemSuit::IntegrationTest ...
Describe your test
This is the description used for a reference to the integration test. At default, GemSuit derives it based on the file name of the integration test.
def description case authentication when :devise "Devise authenticated" else "Non-authenticated" end end
Prepare your Rails dummy application with Thor
This is where you are supposed to run your generators and generate files using templates. You can also prevent source files from automatically included (which is the case for test/suit_application/*.rb*
).
def prepare case config[:authentication] when :devise generate_devise_user correct_devise_config end skip :require, "test/suit_application/rich/i18n_forgery.rb" end def locals_for_template(path) case path when "the/path/to/a/certain/template" {:some_template_variable => "some_value"} end end private def generate_devise_user generate "devise:install" generate "devise", "User" end def correct_devise_config devise_config = expand_path("config/initializers/devise.rb") lines = File.open(devise_config).readlines pepper = "a26c248ff40b12f4e396c1d33168408e2f442c3b6288df70ca46c340db3f1f2f7aa80ec37867ddfd602a185deda0b5efb27ecd8f7541b97d7c02e9485bbb57fd" log :correcting, devise_config File.open(devise_config, "w") do |file| lines.each do |line| file << line.gsub(/(config\.pepper = ").*(")/, "config.pepper = \"#{pepper}\"") end end end
Stash and restore files
As you might have guessed, files are being stashed before testing and restored after testing.
def restore_files delete "config/locales/devise.en.yml" delete "db/migrate/*.rb" delete "test/fixtures/devise_users.yml" delete "test/unit/devise_user_test.rb" end def stash_files delete "db/migrate/*.rb" stash "app/models/*.rb" stash "config/initializers/devise.rb" end
Capybara extensions
You can define helper methods for the integration tests when running with Capybara in capybara_extensions.rb
.
Think of actions such as logging in, logging out and filling in forms:
module GemSuit class IntegrationTest def login visit "/cms" page.execute_script "$('div#rich_cms_dock a.login').click()" fill_in_and_submit "#raccoon_tip", {:Email => "paul.engel@holder.nl", :Password => "testrichcms"}, "Login" end def logout find("#rich_cms_dock").click_link "Logout" end def mark_content page.execute_script "$('div#rich_cms_dock a.mark').click()" end def edit_content(key, css_class = "rcms_content") page.execute_script <<-JAVASCRIPT $(".#{css_class}.marked[data-store_key=#{key}]").click(); JAVASCRIPT assert find("#raccoon_tip").visible? end def fill_in_and_submit(selector, with, submit) within "#{selector} fieldset.inputs" do with.each do |key, value| begin fill_in key.to_s, :with => value rescue Selenium::WebDriver::Error::ElementNotDisplayedError page.execute_script <<-JAVASCRIPT var input = $("#{selector} [name='#{key}']"); if (input.data("cleditor")) { input.val("#{value}"); input.data("cleditor").updateFrame(); } JAVASCRIPT end end end find(selector).find_button(submit).click sleep 2 end end end
Writing the “common” unit tests
The unit tests have to be located within suit/shared/test/unit/
. Run the tests as mentioned earlier with suit test unit
.
Getting more info with Thor
As the GemSuit command line interface (CLI) is built with @wycats Thor gem, you can use the suit help
command in your terminal:
$ suit help Tasks: suit bundle # Run `bundle install` (should be invoked from a Rails dummy application) only when necessary (used for testing) suit config [global] # Configure GemSuit within your gem (use `suit config global` for global config) suit console [ENVIRONMENT] # Start one of the GemSuit test application consoles suit fit # Establish the GemSuit in your environment suit help [TASK] # Describe available tasks or one specific task suit restore # Restore all files within the GemSuit test applications suit server [ENVIRONMENT] # Start one of the GemSuit test application servers suit tailor NAME # Generate a Bundler gem and provide it with GemSuit suit test [SECTION] [FILES] # Run GemSuit (suit, unit, functional, integration) tests suit up # Provide an existing gem with GemSuit
To get info about the options of a command, narrow the output to that command. Just type suit help <command>
:
$ suit help server Usage: suit server [ENVIRONMENT] Options: -p, [--port=PORT] -r, [--rails-version=RAILS_VERSION] Start one of the GemSuit test application servers
So where are the tests?
As GemSuit is very focused on the command line and file generation, it is hard (but not impossible) to write tests. So therefore my test case is the entire Rich-CMS gem. I am using GemSuit for all of its development and testing purposes.
Contact me
For support, remarks and requests please mail me at paul.engel@holder.nl.
License
Copyright © 2011 Paul Engel, released under the MIT license
http://holder.nl – http://codehero.es – http://gettopup.com – http://twitter.com/archan937 – paul.engel@holder.nl
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.