Environments Testing

Blog ยป Environments Testing

Posted on 07 Sep 2010 11:57

A few words about testing related gems, caching and authenticated testing. Bootstrap advice for all your projects.

Gemfile

Use bundler and have this section in your $project/Gemfile

group :test do
  gem "factory_girl"
  gem "shoulda"
  gem "mocha"
  gem "rspec",              "= 1.3.1",  :require => nil
  gem "rspec-rails",        "= 1.3.3",  :require => nil
  gem "steak"
  gem "capybara"
end
 
gem 'ruby-debug', :groups => [:test, :development]

If you are using bundler then config.gem entries in test.rb are redundant.

config/environments/test.rb

Set caching to true in test environment. Otherwise you're not testing what will happen in the production environment.

Also configure testing only gems here. Here is my config/environments/test.rb file:

# Settings specified here will take precedence over those in config/environment.rb
 
# The test environment is used exclusively to run your application's
# test suite.  You never need to work with it otherwise.  Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs.  Don't rely on the data there!
config.cache_classes = true
 
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
 
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching             = true
config.action_view.cache_template_loading            = true
 
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection    = false
 
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
 
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
 
config.action_controller.perform_caching             = true #otherwise caching errors are hidden from tests - umur
 
config.i18n.default_locale = :en
 
config.gem 'thoughtbot-factory_girl', :lib => 'factory_girl', :source => 'http://gems.github.com'
config.gem 'thoughtbot-shoulda', :lib => 'shoulda', :source => 'http://gems.github.com'
config.gem 'rack-test', :lib => 'rack/test'
config.gem 'mocha'
#config.gem 'webrat'
config.gem 'rspec', :lib=>false, :version => '1.3.1'
config.gem 'rspec-rails', :lib=>false, :version => '1.3.3'
config.gem 'steak'
config.gem 'capybara'
#config.gem 'rack-cache'

At least I am using those gems:

Caching is enabled all the way. This way you know tests are passing while you are using the caches!!!

Locale is set to :en (English) intentionally. This way you can have a multi lingual application. But you are validating the application against the English version… Otherwise it's really a pain to test the interface of a multi lingual application.

tests/test_helper.rb

Now in your tests/test_helper.rb,,

  • you need to initialize WebRat
  • turn off fixtures if you are using factory girl
  • have a login_as(user) helper. You want to test authenticated methods right? You need to adapt the method according to the authentication you're using

this is my tests/test_helper.rb

ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'
 
Webrat.configure do |config|
  config.mode = :rails
  #http://gitrdoc.com/rdoc/brynary/webrat/273e8c541a82ddacf91f4f68ab6166c16ffdc9c5/classes/Webrat/Configuration.html
end
 
class ActiveSupport::TestCase
  # Transactional fixtures accelerate your tests by wrapping each test method
  # in a transaction that's rolled back on completion.  This ensures that the
  # test database remains unchanged so your fixtures don't have to be reloaded
  # between every test method.  Fewer database queries means faster tests.
  #
  # Read Mike Clark's excellent walkthrough at
  #   http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
  #
  # Every Active Record database supports transactions except MyISAM tables
  # in MySQL.  Turn off transactional fixtures in this case; however, if you
  # don't care one way or the other, switching from MyISAM to InnoDB tables
  # is recommended.
  #
  # The only drawback to using transactional fixtures is when you actually
  # need to test transactions.  Since your test is bracketed by a transaction,
  # any transactions started in your code will be automatically rolled back.
  self.use_transactional_fixtures = false
 
  # Instantiated fixtures are slow, but give you @david where otherwise you
  # would need people(:david).  If you don't want to migrate your existing
  # test cases which use the @david style and don't mind the speed hit (each
  # instantiated fixtures translates to a database query per test method),
  # then set this back to true.
  self.use_instantiated_fixtures  = false
 
  # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
  #
  # Note: You'll currently still have to declare fixtures explicitly in integration tests
  # -- they do not yet inherit this setting
  # fixtures :all
 
  # Add more helper methods to be used by all tests here...
  class << self
    def could do_sth
      puts "Could #{do_sth}"
    end
    alias feature context
  end
 
  def cleanup
    ActiveRecord::Base.delete_everything!
  end
 
#   def setup; super; cleanup; end
  def teardown; cleanup; super; end
end
 
class ActionController::TestCase
  #http://alexbrie.net/1526/functional-tests-with-login-in-rails/
  #   def login_as(user)
  #     @request.session[:user] = user ? user.id : nil
  #   end
 
  #http://alexbrie.net/1526/functional-tests-with-login-in-rails/
  def login_as(user)
    old_controller = @controller
    @controller = UsersController.new
    post :login, :login=>user.email_address, :password=>user.password
  #   assert_redirected_to :controller => tsap, :action=>'overview'
#     session[:user] = user.typed_id
    assert_not_nil(session[:user])
    @controller = old_controller
  end
end
 
module Rack
  module Utils
    class HeaderHash
      puts "Rack::Utils::HeaderHash bug fix on #replace"
 
      def replace other
        self.clear
        other.each  { |k,v| self[k] = v }
      end
    end
  end
end

Note that use_transactional_fixtures is false. And there is another method to clean up the data. This requires you to follow deleting-all-records-in-rails. Then you will have "ActiveRecord::Base.delete_everything!" defined in your project.

Without such a setting, you cannot test explicit transactions in your code.

There is a fix on class HeaderHash. Without this you cannot rack test, the metal in your application. If this statement is meaningless for you then don't bother. But you might come across the problem in the future.

config/environment.rb

Configure Rack::Cache in environment.rb. This way it will work in development and test but it wont work in production. I am deploying to Heroku. Heroku provides Varnish. Rack::Cache in production is redundant against Varnish and makes you pay a fat bill to Heroku. Include the "unless" block in the config block

#  unless "RAILS_ENV" == "production"
#   require 'rack/cache'
#    config.gem 'rack-cache'
#   config.middleware.use Rack::Cache,
#      :verbose => true,
#      :metastore   => 'file:/tmp/cache/rack/meta',
#      :entitystore => 'file:/tmp/cache/rack/body'
#    # I am deploying to Heroku
#    # Heroku has Varnish which replaces rack-cache
#    # The purpose is to have caching in test and development
#  end

Warning The code above is optional! Use it if and only if you have experience on html page caching. Otherwise you might loose your ability to see updated pages. That's why I kept the example code commented out.

I also keep memcached configuration (Northscale Memcached) in environments.rb. Because I use it both in production and test:

  require 'memcached'
  config.cache_store = :mem_cache_store, Memcached::Rails.new

Note that everybit has to go into Rails::Initializer block in config/environment.rb

spec/spec_helper.rb

I am writing Acceptance Tests using Steak/Capybara.

First initialize the steak environment as described in steak-capybara-on-rails-2-3 executing this bit:

script/generate rspec #Initialize rspec
script/generate steak --capybara #Initialize steak

Then modify the file spec/spec_helper.

Similar to the configuration above, you are disabling transactional fixtures:
- They don't work with Capybara, anyway
- You cannot test transactions in your application

# This file is copied to ~/spec when you run 'ruby script/generate rspec'
# from the project root directory.
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path(File.join(File.dirname(__FILE__),'..','config','environment'))
require 'spec/autorun'
require 'spec/rails'
 
# Uncomment the next line to use webrat's matchers
#require 'webrat/integrations/rspec-rails'
 
# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
 
Spec::Runner.configure do |config|
  # If you're not using ActiveRecord you should remove these
  # lines, delete config/database.yml and disable :active_record
  # in your config/boot.rb
  config.use_transactional_fixtures = false
  config.use_instantiated_fixtures  = false
  config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
 
  # == Fixtures
  #
  # You can declare fixtures for each example_group like this:
  #   describe "...." do
  #     fixtures :table_a, :table_b
  #
  # Alternatively, if you prefer to declare them only once, you can
  # do so right here. Just uncomment the next line and replace the fixture
  # names with your fixtures.
  #
  # config.global_fixtures = :table_a, :table_b
  #
  # If you declare global fixtures, be aware that they will be declared
  # for all of your examples, even those that don't use them.
  #
  # You can also declare which fixtures to use (for example fixtures for test/fixtures):
  #
  # config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
  #
  # == Mock Framework
  #
  # RSpec uses its own mocking framework by default. If you prefer to
  # use mocha, flexmock or RR, uncomment the appropriate line:
  #
  # config.mock_with :mocha
  # config.mock_with :flexmock
  # config.mock_with :rr
  #
  # == Notes
  #
  # For more information take a look at Spec::Runner::Configuration and Spec::Runner
 
  #add information to log about what spec is currently run
  config.before(:each) do
    full_example_description = "Starting #{@method_name}"
    Rails::logger.info("\n\n#{full_example_description}\n#{'-' * (full_example_description.length)}")      
  end  
 
  config.after(:each) do
    Rails::logger.info('clean up data')      
    ActiveRecord::Base.delete_everything!
  end

spec/acceptance/support/helpers.rb

If you are using steak tests then you need to have a way to log in during the test. So provide logger and login_as methods in addition to other things you might have:

#spec/acceptance/support/helpers.rb
 
module HelperMethods
  # Put here any helper method you need to be available in all your acceptance tests
 
  def logger
    Rails::logger
  end
 
  def login_as(user)
    visit '/login'
    fill_in 'login', :with => user.email_address
    fill_in 'password', :with => user.password
    click_button 'Log in'
  end
 
end
 
Spec::Runner.configuration.include(HelperMethods)

If you like this page, please spread the word: diggdel.icio.usFacebook

You can contact me if you have questions or corrections.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License