Showing posts with label testing. Show all posts
Showing posts with label testing. Show all posts

19 February, 2008

with_modules_unavailable and some Module helpers

I've been trying to test a new Rails plugin (stay tuned!), and I've found that I need to have certain Modules unavailable. This is useful when you need to test that missing Modules will raise errors or if you have behavior conditional on what gems are installed.

I wanted something like:

...
with_modules_unavailable(Foo, Bar::Baz::Goo) do
  test stuff
end

To get this working, just put the following in your test_helper.rb (this version requires Inflector):

Module.class_eval do
  
  def defining_module
    chain = self.to_s.split(/\:\:/)
    chain.pop
    if chain.empty?
      Object
    else
      chain.join('::').constantize
    end
  end
  
  def simple_name
    self.to_s.split(/\:\:/).pop
  end
  
end

Test::Unit::TestCase.class_eval do

  def with_modules_unavailable(*mods, &block)
    Thread.exclusive do
      mods.each do |mod|
        mod.defining_module.send :remove_const, mod.simple_name.to_sym
      end
      yield block
      mods.reverse.each do |mod|
        mod.defining_module.send :const_set, mod.simple_name.to_sym, mod
      end
    end
  end
  
end

You can test that it works with:

def test_with_modules_unavailable
  with_modules_unavailable(ActiveRecord::Base) do
    assert_raise(NameError) { ::ActiveRecord::Base.class_eval { } }
  end
  assert_nothing_raised { ::ActiveRecord::Base.class_eval { } }
end

01 July, 2007

and the test_spec_on_rails journey continues

Test/Spec has a wonderful - if largely unnoticed - feature: you can specify the superclass of contexts. The default is the ole' Test::Unit::TestCase, but that won't always do.

Let's say, hypothetically, you wanted to write some integration tests for your not-quite-shiny Rails app. You'd use ApplicationController::IntegrationTest, right? That way you can do things like get "/posts/34.html" and it would look up the routings and just do the right thing. Awesome.

Except... things aren't quite so simple when using test/spec. Therefore, I bring you an illustrious, illustrative example:

require File.dirname(__FILE__) + '/../test_helper'

class UserStoriesTest < ActionController::IntegrationTest
 fixtures :people, :openid_authentications, :password_authentications
 context "User Stories", ActionController::IntegrationTest do
   context "a person coming to the site to log in", ActionController::IntegrationTest do
     specify "should see the welcome page" do
       get '/'
       template.should.be 'welcome/index'
       status.should.be 200
     end
     specify "should be able to successfully log in with email and password..." do
       post_via_redirect '/login.html', {:email => 'pete.thomas@xahoo.com', :password => 'test'}
       template.should.be 'people/home'
       status.should.be 200
     end
   end
 end

end

test_spec_on_rails continued...

Yesterday, I was very happy because all my controller tests (using test/spec, of course) were passing individually.

Today I decided to run rake test:functionals just to make sure they played well together. CRASH! BOOM! OTHER LOUD NOISES!

The problem, after two hours of debugging, is that I was creating the same context in different controllers. DocumentationControllerTest had a "A guest" context, and so did "AccountControllerTest" and "WelcomeControllerTest"

Test/spec is perfectly happy to merge these . . . I just didn't realize it merged them. I assumed that it would scope the context to its parent TestClass. No such luck.

The solution is quite easy, and really not all that bad practice anyway: scope your contexts manually. For example:

class WelcomeControllerTest < Test::Unit::TestCase
 context "The Welcome Controller" do
   context "A guest" do
     ...
   end
   context "A logged-in user" do
     ...
   end
 end
end