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:

  1. Start with a clean Rails (2 or 3) application (as if you just ran rails new your_rails_app)
  2. Prep certain files (e.g. initializers) using Thor’s template generation capabilities
  3. Run several generators provided by your gem
  4. Load fixtures
  5. 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 to SuitApplication)
  • 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 
      visit "/cms"
      page.execute_script "$('div#rich_cms_dock a.login').click()"
      fill_in_and_submit "#raccoon_tip", {:Email => "[email protected]", :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 [email protected].

License

Copyright © 2011 Paul Engel, released under the MIT license

http://holder.nlhttp://codehero.eshttp://gettopup.comhttp://twitter.com/archan937[email protected]

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.