Full Stack Testing a HOBO Application

Blog ยป Full Stack Testing a HOBO Application

Posted on 03 Dec 2010 18:10

All testing strategies and dsl covered in one simple Hobo application. Hands on introduction the unit, functional and acceptance tests follow here. If you are only insterested in testing bits then jump directly to that section.

Important Note! Hobo for Rails 3 is still Beta. Therefore in production, I am still using Rails 2…. When Hobo for Rails 3 is out of Beta it will be a heaven. Counting down. Soon… Thus this article and related pages are written on Rails 2.

Quick steps to make a hobo application. Let's have a database of organizations with a name.

123, Serve

Goto your console and:

hobo organizations
cd organizations
script/generate hobo_model_resource Organization name:string

Now edit app/models/organization.rb and enhance the name field definition:

  fields do
    name :string, :required, :unique, :null=>false, :index=>true
    timestamps
  end

And continue on the console:

script/generate hobo_migration
rake db:migrate
script/server

Try your application at http://localhost:3000

Preparation

If you don't have Rails and/or Hobo then….
Do Ruby on the Rails Installation. Then install hobo:

sudo gems install hobo

Hobo Fields API

name :string, :required, :unique

:required and :unique are parameters of Hobo Fields API. They add validations to the field. Note that they only add validations. They do not modify the database.

Hobo Migrations

name :string, :required, :unique, :null=>false, :index=>true

:null=>false and :index=>true are process by Hobo Migration Generator. They modify the database schema! Now :name field has a unique index as well and it cannot be :null.

Migration parameters must follow field API parameters! The code below will not work!

name :string, :required, :null=>false, :unique , :index=>true
#Wrong hobo code

Multi field indexes

An example of declaring indexes over multiple fields:

fields do ... end
index [:first_name, :last_name], :unique=>true

In addition to the ability to specify an index per field, you can add indexes over multiple fields to ensure uniqueness.

Your First DRYML

#TODO

Populating Database

You can use FactoryGirl to populate the databse during development. You will need Factories for test. But I don't see any reason that you cannot use them in development environment also.

First create those 2 factory files:

#test/factories/user.rb
require 'factory_girl'
 
Factory.define :admin, :class=> User do |u|
  u.sequence(:name ){|n| "admin#{n}"}
  u.sequence(:email_address ){|n| "admin#{n}@test.com"}
  u.password 'pass'
  u.administrator true
end
 
Factory.define :user do |u|
  u.sequence(:name ){|n| "user#{n}"}
  u.sequence(:email_address ){|n| "user#{n}@test.com"}
  u.password 'pass'
  u.administrator false
end
#test/factories/organzation.rb
require 'factory_girl'
 
Factory.define :organization do |m|
  m.sequence(:name) {|n| "Test Organization #{n}"}
end

Then a rake task to populate your database:

#lib/tasks/app_populate.rake
namespace :app do
desc 'Rebuild the database from scratch'
 
desc 'Fill database with test data'
task :populate=> :environment do
  Rake::Task["db:reset"].invoke
 
  require 'factory_girl'
  Dir['test/factories/*.rb'].each { |f| load f }
 
  p "Manifacturing Objects..."
  (1..2).each  { Factory(:organization) }
end
 
end

You can search for rake tasks:

rake -T populate

You should see the newly defined task now.

Now go and type the command:

rake app:populate

Fire your server and browse to http://localhost:3000

script/server

Now you will see the populated application to try.

This way you are trying out data to use in tests also.

Below I will present repeating test patterns so that you can repeat them in your projects. Like above, you need to use the source as it is only.

Testing

  • Test are like declarations in typed language. I can make specification. Only authors and admins can edit blog posts. Blog titles are unique. An author can create/read/update/delete a blog entry, etc. They are the declarations about your application!
  • They provide a better environment for debugging then console. It you'll repeatedly execute something then thats a test.

We will go over those testing strategies

  • Unit tests
  • Functional tests
  • Acceptance tests

Each has its own powerful points and better suits to certain tasks.

After experiencing the strategy here, I hope you will be able to repeat it in other projects leading to test-first-implementation.

That's a concern if one does not know the testing DSLs then one cannot go test-first (Extreme Programming)

Preparing Test Environment

First follow deleting-all-records-in-rails
Then follow environments-testing to make the necessary changes to project configuration files… In time you'll need all those config changes and doing them one by one is a loss of time.

Make sure that you've initialized the steak environment as described in steak-capybara-in-rails-2-3 executing this bit:

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

Then execute

sudo rake gems:install RAILS_ENV=test

Now your machine is configured for testing

Unit Tests

Edit test/unit/organization_test.rb:

#test/unit/organization_test.rb
require File.dirname(__FILE__) + '/../test_helper'
 
#See
#    https://github.com/thoughtbot/shoulda
 
class OrganizationTest < ActiveSupport::TestCase
  context "ActiveRecord" do
    setup { Factory(:organization)}
    should validate_uniqueness_of( :name )
  end
  context "An Organization" do
    setup { @organization = Factory(:organization)}
    should "do something"
    should "do anotherthing"
  end
end

context "ActiveRecord" is declarations about your model. Always provide this context complete with declarations about your model. Refer to https://github.com/thoughtbot/shoulda for all ActiveRecord tests

context "Organization is just an example of how you should phrase your tests.

Execute

rake unit:tests

You'll see DEFERRED: An Organization should do something.

So a context is a the subject of your test. Then you say what it should do. Always start with empty should blocks s to check that it reads correctly.

You've executed your first test and verified the declaration of the model.

Designing Functional Tests

Same principle again. Start with empty blocks and make sure the test reads correctly:

Edit test/functional/organizations_controller_test.rb

require File.dirname(__FILE__) + '/../test_helper'
 
#See
#   https://github.com/thoughtbot/shoulda
#   http://guides.rubyonrails.org/testing.html#functional-tests-for-your-controllers
 
# GET    /items        #=> index
# GET    /items/1      #=> show
# GET    /items/new    #=> new
# GET    /items/1/edit #=> edit
# PUT    /items/1      #=> update
# POST   /items        #=> create
# DELETE /items/1      #=> destroy
 
class OrganizationsControllerTest < ActionController::TestCase
  context "Security:  " do
    setup { 
      @organization = Factory(:organization)
      @attrs = Factory.attributes_for(:organization) }
    context "Guest" do
      #setup {login_as somebody}
      context "(read_actions)" do
        should "get index"
        should "get show"
      end
      context "(edit actions)" do
        should "not get new"
        should "not get edit"
      end
      context "(write_actions)" do
        should "not post create" 
        should "not put update"
        should "not delete" 
      end
    end
  end
end

Execute:

rake test:functionals

And check how the test reads. Got the idea?

Securing the Site Using Functional Tests

Edit test/functional/organizations_controller_test.rb again:

require File.dirname(__FILE__) + '/acceptance_helper'
 
#See
#   https://github.com/thoughtbot/shoulda
#   http://guides.rubyonrails.org/testing.html#functional-tests-for-your-controllers
 
# GET    /items        #=> index
# GET    /items/1      #=> show
# GET    /items/new    #=> new
# GET    /items/1/edit #=> edit
# PUT    /items/1      #=> update
# POST   /items        #=> create
# DELETE /items/1      #=> destroy
 
class OrganizationsControllerTest < ActionController::TestCase
  context "Security:  " do
    setup { 
      @organization = Factory(:organization)
      @attrs = Factory.attributes_for(:organization) }
    context "Guest" do
      #setup {login_as somebody}
      context "(read_actions)" do
        should "get index" do
          get :index
          assert_response :success
        end
        should "get show" do
          get :show, :id=>@organization.id
          assert_response :success
        end
      end
      context "(edit actions)" do
        should "not get new" do
          get :new
          assert_response :success
          assert_no_tag :tag=>'form'
        end 
        should "not get edit" do
          get :edit, :id=>@organization.id
          assert_response :success
          assert_no_tag :tag=>'form'
        end
      end
      context "(write_actions)" do
        should "not post create" do
          count1 = Organization.count
          post :create, :organization => @attrs 
          count2 = Organization.count
          assert_equal count1, count2, "Nothing created"
          assert_response :forbidden
        end
        should "not put update" do
          put :update, :id=>@organization.id, :organization => @attrs 
          assert_response :forbidden
        end
        should "not delete" do
          delete :destroy, :id=>@organization.id
          assert_response :forbidden
        end
      end
    end
  end
end

and execute:

rake test:functionals

You will see that there is a failing test. Follow http://conceptspace.wikidot.com/blog:hobo-security-hole and try again.

You never know if your security will fail because of an upgrade or changes in your system. You can never assume your application is secure because you've coded the right way.

Provide a 'context "Security"' for all your controller tests and use other contexts for your other tests. This way context "Security" can be peer reviewed easily. We are checking the security there. And it repeats almost the same for different controllers.

So functinal tests are a perfect place to verify authentication constraints. This way you are declaring the existence of actions also.

Note that the actions are grouped as edit, read and write. This way if you have to define a custom action, you can add the it to the related group and repeat the same test pattern. All actions in the same group are tested almost the same way.

You've declared the authentication constraints of your application and proved that your application is secure.

Ruby Debugger

While running all kinds of tests you can use the rails debugger to solve your problem

Edit models/organization.rb

require File.dirname(__FILE__) + '/acceptance_helper'
 
class Organization < ActiveRecord::Base
 
  hobo_model # Don't put anything above this
 
  fields do
    name :string, :required, :unique, :null=>false, :index=>true
    timestamps
  end
  #index [:name], :unique=>true
 
  # --- Permissions --- #
 
  def create_permitted?
#     puts "create_permitted? #{acting_user.administrator?}"
    debugger #THIS IS THE LINE TO ADD TO INVOKE DEBUGGER
    acting_user.administrator?
  end
 
  def update_permitted?
    acting_user.administrator?
  end
 
  def destroy_permitted?
    acting_user.administrator?
  end
 
  def view_permitted?(field)
    true
  end
 
end

And execute again:

rake test:functionals

You will end up in the debugger.

Type 'help' or 'help command' to learn about the facilities.

Most frequently you'll use those commands:

l=     
Show current line
n      
Execute next line
s      
Step into next line
h      
Help
bt     
See the call stack
pp self     
Pretty print the current object

Designing Acceptance Tests

Edit spec/acceptance/organization_crud_spec.rb

require File.dirname(__FILE__) + '/acceptance_helper'
 
#See 
#    https://github.com/cavalle/steak
#    https://github.com/jnicklas/capybara
#    https://github.com/thoughtbot/factory_girl
 
feature "Organization CRUD", %q{
  As an Admin
  I want to CRUD organizations
} do
 
background {@attrs = Factory.attributes_for(:organization)}
 
  describe "When admin, " do
    background do
      @admin = Factory(:admin)
      login_as(@admin)
    end
 
    describe "With nothing, " do
      describe "At list, " do
        background {visit '/organizations'}
        scenario "I browse"
        scenario "I create"
      end
    end
 
    describe "With a record, " do
      background {@organization = Factory(:organization)}
      describe "At list, " do
        background {visit '/organizations'}
        scenario "I browse"
        describe "At show, " do
          background do
            within('.collection.organizations .card.organization') do
              click_link @organization.name
            end
          end
          scenario "I read"
          describe "At edit, " do
            background {find('.edit-link.organization-link').click}
            scenario "I update"
            scenario "I delete"
          end
        end
      end
    end
 
  end
 
#   describe "When guest, " do
#   end
 
end

and execute

rake spec:acceptance

Same principle again. When designing your own tests. Use empty scenarios to see that it reads correctly.

Describing User Experience

Again edit spec/acceptance/organization_crud_spec.rb

require File.dirname(__FILE__) + '/acceptance_helper'
 
#See 
#    https://github.com/cavalle/steak
#    https://github.com/jnicklas/capybara
#    https://github.com/thoughtbot/factory_girl
 
feature "Organization CRUD", %q{
  As an Admin
  I want to CRUD organizations
} do
 
background {@attrs = Factory.attributes_for(:organization)}
 
  describe "When admin, " do
    background do
      @admin = Factory(:admin)
      login_as(@admin)
    end
 
    describe "With nothing, " do
      describe "At list, " do
        background {visit '/organizations'}
        scenario "I browse" do
          page.should_not have_content(@attrs[:name])
        end
        scenario "I create" do
          find('.new-link.new-organization-link').click
          fill_in 'organization[name]', :with => @attrs[:name]
          click_button 'Create Organization'
          visit('/organizations')
          within('.collection.organizations .card.organization') do
            page.should have_content @attrs[:name]
          end
        end
      end
    end
 
    describe "With a record, " do
      background {@organization = Factory(:organization)}
      describe "At list, " do
        background {visit '/organizations'}
        scenario "I browse" do
          within('.collection.organizations .card.organization') do
            page.should have_content(@organization.name)
          end
          page.should_not have_content(@attrs[:name])
        end
        describe "At show, " do
          background do
            within('.collection.organizations .card.organization') do
              click_link @organization.name
            end
          end
          scenario "I read" do
            page.should have_content @organization.name
          end
          describe "At edit, " do
            background {find('.edit-link.organization-link').click}
            scenario "I update" do
              fill_in 'organization[name]', :with => @attrs[:name]
              click_button 'Save'
              visit('/organizations')
              within('.collection.organizations .card.organization') do
                page.should have_content @attrs[:name]
              end
            end
            scenario "I delete" do
              find('.delete-button').click
              visit('/organizations')
                page.should_not have_css('.collection.organizations .card.organization')
                page.should_not have_content(@organization.name)
            end
          end
        end
      end
    end
 
  end
 
#   describe "When guest, " do
#   end
 
end

*The principle: * We are doing CRUD (create read update delete) on our objects. In addition to other features always have a crud_spec.rb for any object you have. This is a pattern to repeat for all projects and objects…

Be aware, we are not concerned about security here! We are not specifying what one cannot do! It's too verbose and too long.

We are concerned with what can be done! We have specified security constraints already in functional tests. We are specifying here what a certain type of user is doing to use our system. It's usage scenarios.

Also we are not specifying model constraints here. It's handled in unit tests. Organizations have a unique name. It's already done.

Here we are proving that the feature exists.

Here we are proving that the user can use the system as intended.

If something is beyond crud, security and object constraints then you have worthy feature do describe. Go and make your new feature spec file then.

Repeating the Pattern

The principles so far are

  • In unit tests, always have context "ActiveRecord" and declare your model constraints there.
  • In functional tests, always have a context "Security" and verify access rights there.
  • In acceptance tests, specify the positive user experience. Negative user experience is specified elsewhere. The exception to this is you need to verify that the user is getting a custom user interface on negative experience. But this is another feature… Saying that the user sees such a message when names are unique is altogether another feature.

Finally running

rake test
rake spec::acceptance

executes all the tests. This is what should be done

  • After checking out a new project
  • Before merging someone else's branch
  • After an upgrade in your computer and libraries

And of course make sure that somebody is not deceiving you by providing empty tests.

Even if you run out of imagination, always use the repeating test pattern above. This guarantees that the system is running always.

Now you can go ahead to your own projects and do them "test-first"

References

Testing References

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