Funktional

A Rails testing framework with a railsy syntax

Install

  • Via the gem

    gem install funktional
    
  • With braid

    braid add git://github.com/brentgreeff/funktional.git -p
    
  • add a setup line to ‘test/test_helper.rb’

    class ActiveSupport::TestCase
      setup :funktional
    end
    

Done!

For extra flavour, add:

* should_pricot (Hpricot matchers in Test::Unit)
* hash_factory (Super simple factories for your tests)
* matchy (RSpec matchers in Test::Unit)

Contexts

Contexts, Don’t Repeat Yourself, share common setup steps between your tests.

context "On a hot summer day" do
  before { day :hot }

  context "in the middle of July" do
    before { Time.now = 'July 15' }

    should "be on the beach" do
      assert_equal 'beach', @me.location
    end

    should "be drinking lemonade" do
      assert_equal 'lemonade', @me.drinking
    end
  end
end

Unit tests

Define domain logic through validations.

  • Start off with a valid instance

    context "A Company" do
      setup { @company = create_company }
    
      should "respond to fax no" do
        @company.should_respond_to :fax_no
      end
    
      should "require a name" do
        @company.should_require_a :name, 'please enter the name'
      end
    
      should "require an address" do
        @company.should_require_an :address, 'please enter the address'
      end
    
      should "not allow creative accounting" do
        @company.creative_accounting = true
        @company.should_have_invalid :books, 'no creative accounting please'
      end
    
      should "not require a telephone no if an address is present" do
        @company.address = an_address
        @company.should_not_require_a :telephone_no
      end
    
      should "not have a name longer than 80 characters" do
        @company.name = 81.random_characters
        @company.should_have_invalid :name, 'max is 80'
        # random_characters is a small useful helper method.
      end
    end
    

Email

should "send email" do
  should :send_email => {
    :from => '[email protected]',
    :to => '[email protected]',
    :subject => 'Your order',
    :containing => "important info"
  }
end

should "not send email" do
  should_not :send_email do
    # do something here
  end
end

Object creation

should "create something" do
  should :create => Something do
    # Do something
  end
end

should "delete something" do
  should :delete => Something do
    # Do something
  end
end

should "not delete something" do
  should_not :delete => Something do
    # Do something
  end
end

should "not create something" do
  should_not :create => Something do
    # Do something
  end
end

Testing Controllers

Routing

should :route => '/onions/new' do
  controller 'onions'
  action 'new'
end
  • you need to pass the method if its not a :get request

    should :route => '/onions', :method => :post do
      controller 'onions'
      action 'create'
    end
    

Render

should "show the new order page" do
  get :new
  should :render => 'orders/new'
end

The default ‘should :render’ checks for a http status code of 200

  • What about other codes?

    should "return the not found page when the id does not exist" do
      get :show, :id => 'something does not exist'
      should :render_404 => 'public/404'
    end
    

Redirection

should "go to the login page if not logged in" do
  logout
  get :new
  should :redirect_to => '/login'
end

Initializing a new object

should "assign a new order" do
  get :new
  should :assign_new => Order
end

Loading Objects

should "load order by id" do
  get :edit, :id => @order.id
  assigned(Order).should_be @order
end
  • This checks the object assigned is of the correct type.

Testing the attributes of an assigned object.

should "associate the current user as the editor" do
   @user = create_user
  put :update, :id => @article.id

  assigned(Article).editor.should_be @user
end

should "chain as long as you like" do
  assigned(Article).editor.first_name.should_be 'pete'
end
  • If you pass a Symbol its just a value based assertion.

    should "load a collection" do
      get :index
      assigned(:records).should_be [@record_1, @record_2]
    end
    

Flash messages

should "notify the user when order was created" do
  post :create, :order => attrib
  flashed(:notice).should_be 'Yay, Order created!'
end

Controller Helpers

There are also some helpers for manipulating attributes.

I tend to define an attrib method in my funtional tests to represent valid attributes passed to create or update a resource.

  • eg:

    def attrib
      {
        :first_name => 'Jim',
        :last_name => 'Bean'
      }
    end
    

Sometimes you want to tests different values, especially invalid ones, to get away from all the merge noise, I have defined these helpers:

missing_attrib
blank_attrib
replace_attrib
add_attrib
  • Means you can write tests like the following:

    should_not :create => Order do
      post :create, :order => blank_attrib(:cc_no)
    end
    should :render => 'orders/new'
    

All funktional assertions are also available as class methods.

context "When doing it all" do
  before { :hit_a_controller }

  should :render => 'somethings/new'

  should :render_404 => 'public/404'

  should :render_404    # (defaults to 'public/404')

  should :redirect_to => '/somethings'

  element('h1').should_be 'Something'   # (you need should_pricot for this one).

  count('#friends ol.remaining').should_be 'Not Many'   # (should_pricot here too)

  flashed(:notice).should_be 'Cool'

  should :assign_new => Something

  assigned(Something).name.should_be 'something'

  assigned(:something).should_be { @something }

  should :create => Something

  should :delete => Something

  should_not :create => Something

  should_not :delete => Something

  should :send_email => {
    :from => '[email protected]',
    :to => '[email protected]'
  }

  should_not :send_email
end

Copyright © 2009 [Brent Greeff], released under the MIT license