Logo

Message / SOA Testing...

over 2 years ago | Riju Kansal: Riju's Thoughts Captured...

Recently I have started working for a testing project in the financial domain. The application we need to test uses a lot messaging architectur {SOA}. So basically a number of systems interact with each other by sending messages to each other and receiving responses in asynchronous fashion.

So automating the testing for the messaging framework is very crucial. We are using a licenced software called Green Hat for this task. In this tool we basically create publishers and subscribers to various Queues. We create XML messages and send them to Publisher Queues and verify the responses using Subscriber Queues or Database calls.

This Green Hat being licenced tool I was wondering if we can find out an open source way of doing this. And I found the following link in the very first search results.

http://www.ibm.com/developerworks/websphere/library/techarticles/0808_vandekuil/0808_vandekuil.html

This is basically using JMeter for the same purpose.

How to use dates in Rails when your database stores a string

over 2 years ago | Alex Rothenberg: Common Sense Software

When working with Rails there's a lot of magic that happens behind the scenes to make it easy to do complex things. Most of the time you don't need to know how that "magic" works but there are times when things don't work as expected and its helpful to dig in and understand what Rails is doing under the covers so you can change how it works. Did I just say "change how Rails works"?!? I did! Rails is opinionated software that seeks to lead you down the golden path but there are legitimate times when you have to veer off that path and Rails lets you do so. I find this most often happens to me when I'm dealing with an existing legacy database which is not setup as Rails would like.

Today I'm going to go through an example that happened to me recently when I had an existing database that stored some dates in a text column but I needed to treat them as dates in my UI. I couldn't change the type of that column as there was another legacy application that expected it to be text.

Using Dates the Rails Way

First let's look at how easy it is to work with dates when you can follow the Rails Way. Let's create a new project and add a scaffolded Person object with a date attribute called birthday.

rails date_select_example
cd date_select_example
script/generate scaffold person name:string birthday:date
rake db:migrate



Now if we hit the site and try to create a new person we see a screen like this



And when you click "Create"



Exactly what you want with almost no code in the view and none in the model. I did make a minor edit to the view so we would get 1960 in the year select by adding :start_year=>1900 (a full documentation of date_select options are available here)

#app/views/people/new.html.erb
<h1>New person
<% form_for(@person) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :birthday %><br />
    <%= f.date_select :birthday, :start_year=>1900 %>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

<%= link_to 'Back', people_path %>



and

#app/models/person.rb
class Person < ActiveRecord::Base
end



Errors when storing as text in the database

Now what happens when you run into a case where the date is stored as a string in the database. Let's say we have an "anniversary" attribute stored as a string that we want to treat the same way as we did birthday. We create the migration.

script/generate migration AddAnniversaryToPerson anniversary:string
rake db:migrate



Then add the anniversary to our view.

#app/views/people/edit.html.erb
<h1>Editing person
<% form_for(@person) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :birthday %><br />
    <%= f.date_select :birthday, :start_year=>1900  %>
  </p>
  <p>
    <%= f.label :anniversary %><br />
    <%= f.date_select :anniversary, :start_year=>1900  %>
  </p>
  <p>
    <%= f.submit 'Update' %>
  </p>
<% end %>

<%= link_to 'Show', @person %> |
<%= link_to 'Back', people_path %>





It looks like we're done so we click "Update" and .. Oops. It doesn't work! We get the error 1 error(s) on assignment of multiparameter attributes. Now we need to figure out what multiparameter attributes are and whey they're not working for us.

ActiveRecord::MultiparameterAssignmentErrors in PeopleController#update

1 error(s) on assignment of multiparameter attributes
RAILS_ROOT: /Users/alexrothenberg/date_select_example

Application Trace | Framework Trace | Full Trace
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:3061:in `execute_callstack_for_multiparameter_attributes'
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:3022:in `assign_multiparameter_attributes'
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:2749:in `attributes='
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:2627:in `update_attributes'
/Users/alexrothenberg/date_select_example/app/controllers/people_controller.rb:63:in `update'
...more stack trace...

Request Parameters:

{"commit"=>"Update",
 "_method"=>"put",
 "authenticity_token"=>"qezkVq+MNzFuXxFBJ/GaSoh2BNdxM6oF3H7JP5beFFE=",
 "id"=>"1",
 "person"=>{"name"=>"Barack Obama",
 "birthday(2i)"=>"8",
 "birthday(3i)"=>"4",
 "anniversary(1i)"=>"2009",
 "anniversary(2i)"=>"5",
 "anniversary(3i)"=>"22",
 "birthday(1i)"=>"1960"}}



What happened? There are two keys to figuring out what's going on


  1. The date_select helper actually sends 3 http parameters to our application anniversary(1i), anniversary(2i) and anniversary(3i). ActiveRecord must combine those into a single Date before updating the row in the database.
  2. Looking at assign_multiparameter_attributes in active_record/base.rb we see this this comment that talks about combining 3 http parameters into a date type by calling new on the column type
    Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done by calling new on the column type or aggregation type (through composed_of) object with these parameters.
    So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.



Now we know we're close to the right place and can check the column's return type using script/console

  $ script/console
  Loading development environment (Rails 2.3.2)
  >> Person.columns_hash['birthday']
  => #
  >> Person.columns_hash['birthday'].klass
  => Date
  >> Person.columns_hash['anniversary']
  => #
  >> Person.columns_hash['anniversary'].klass
  => String



This makes sense. ActiveRecord is treating the anniversary column as a string because that's what it is in the database so is not combining the 3 multiparameter attributes into a Date. What we need to do is override the default ActiveRecord logic and tell it to treat this column as a date. We can write a failing spec

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

describe Person do
  it "should treat anniversary as a Date column" do
    Person.columns_hash['anniversary'].klass.should == Date
  end
end

# 'Person should treat anniversary as a Date column' FAILED
# expected: Date,
#      got: String (using ==)



Then take advantage of a trick of Ruby that allows you to extend an object without affecting other instances of its class (see PickAxe book's explanation of this technique). If we lookup the definition of klass for a column It is actually very simple to implement our fix with the 5 lines below.

class Person < ActiveRecord::Base
  class << columns_hash['anniversary']
    def type
      :date
    end
  end
end



Now our tests pass and when we go back to our site and click the Update button and it works.




How to use dates in Rails when your database stores a string

over 2 years ago | Alex Rothenberg: Common Sense Software

When working with Rails there's a lot of magic that happens behind the scenes to make it easy to do complex things. Most of the time you don't need to know how that "magic" works but there are times when things don't work as expected and its helpful to dig in and understand what Rails is doing under the covers so you can change how it works. Did I just say "change how Rails works"?!? I did! Rails is opinionated software that seeks to lead you down the golden path but there are legitimate times when you have to veer off that path and Rails lets you do so. I find this most often happens to me when I'm dealing with an existing legacy database which is not setup as Rails would like.

Today I'm going to go through an example that happened to me recently when I had an existing database that stored some dates in a text column but I needed to treat them as dates in my UI. I couldn't change the type of that column as there was another legacy application that expected it to be text.

Using Dates the Rails Way

First let's look at how easy it is to work with dates when you can follow the Rails Way. Let's create a new project and add a scaffolded Person object with a date attribute called birthday.

rails date_select_example
cd date_select_example
script/generate scaffold person name:string birthday:date
rake db:migrate



Now if we hit the site and try to create a new person we see a screen like this



And when you click "Create"



Exactly what you want with almost no code in the view and none in the model. I did make a minor edit to the view so we would get 1960 in the year select by adding :start_year=>1900 (a full documentation of date_select options are available here)

#app/views/people/new.html.erb
<h1>New person
<% form_for(@person) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :birthday %><br />
    <%= f.date_select :birthday, :start_year=>1900 %>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

<%= link_to 'Back', people_path %>



and

#app/models/person.rb
class Person < ActiveRecord::Base
end



Errors when storing as text in the database

Now what happens when you run into a case where the date is stored as a string in the database. Let's say we have an "anniversary" attribute stored as a string that we want to treat the same way as we did birthday. We create the migration.

script/generate migration AddAnniversaryToPerson anniversary:string
rake db:migrate



Then add the anniversary to our view.

#app/views/people/edit.html.erb
<h1>Editing person
<% form_for(@person) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :birthday %><br />
    <%= f.date_select :birthday, :start_year=>1900  %>
  </p>
  <p>
    <%= f.label :anniversary %><br />
    <%= f.date_select :anniversary, :start_year=>1900  %>
  </p>
  <p>
    <%= f.submit 'Update' %>
  </p>
<% end %>

<%= link_to 'Show', @person %> |
<%= link_to 'Back', people_path %>





It looks like we're done so we click "Update" and .. Oops. It doesn't work! We get the error 1 error(s) on assignment of multiparameter attributes. Now we need to figure out what multiparameter attributes are and whey they're not working for us.

ActiveRecord::MultiparameterAssignmentErrors in PeopleController#update

1 error(s) on assignment of multiparameter attributes
RAILS_ROOT: /Users/alexrothenberg/date_select_example

Application Trace | Framework Trace | Full Trace
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:3061:in `execute_callstack_for_multiparameter_attributes'
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:3022:in `assign_multiparameter_attributes'
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:2749:in `attributes='
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:2627:in `update_attributes'
/Users/alexrothenberg/date_select_example/app/controllers/people_controller.rb:63:in `update'
...more stack trace...

Request Parameters:

{"commit"=>"Update",
 "_method"=>"put",
 "authenticity_token"=>"qezkVq+MNzFuXxFBJ/GaSoh2BNdxM6oF3H7JP5beFFE=",
 "id"=>"1",
 "person"=>{"name"=>"Barack Obama",
 "birthday(2i)"=>"8",
 "birthday(3i)"=>"4",
 "anniversary(1i)"=>"2009",
 "anniversary(2i)"=>"5",
 "anniversary(3i)"=>"22",
 "birthday(1i)"=>"1960"}}



What happened? There are two keys to figuring out what's going on


  1. The date_select helper actually sends 3 http parameters to our application anniversary(1i), anniversary(2i) and anniversary(3i). ActiveRecord must combine those into a single Date before updating the row in the database.
  2. Looking at assign_multiparameter_attributes in active_record/base.rb we see this this comment that talks about combining 3 http parameters into a date type by calling new on the column type
    Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done by calling new on the column type or aggregation type (through composed_of) object with these parameters.
    So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.



Now we know we're close to the right place and can check the column's return type using script/console

  $ script/console
  Loading development environment (Rails 2.3.2)
  >> Person.columns_hash['birthday']
  => #
  >> Person.columns_hash['birthday'].klass
  => Date
  >> Person.columns_hash['anniversary']
  => #
  >> Person.columns_hash['anniversary'].klass
  => String



This makes sense. ActiveRecord is treating the anniversary column as a string because that's what it is in the database so is not combining the 3 multiparameter attributes into a Date. What we need to do is override the default ActiveRecord logic and tell it to treat this column as a date. We can write a failing spec

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

describe Person do
  it "should treat anniversary as a Date column" do
    Person.columns_hash['anniversary'].klass.should == Date
  end
end

# 'Person should treat anniversary as a Date column' FAILED
# expected: Date,
#      got: String (using ==)



Then take advantage of a trick of Ruby that allows you to extend an object without affecting other instances of its class (see PickAxe book's explanation of this technique). If we lookup the definition of klass for a column It is actually very simple to implement our fix with the 5 lines below.

class Person < ActiveRecord::Base
  class << columns_hash['anniversary']
    def type
      :date
    end
  end
end



Now our tests pass and when we go back to our site and click the Update button and it works.




Beautiful

over 2 years ago | Surbhi Bhati: Clean Desk, Jammed drawers

Deep in my heart it pains..
nothing like joy remains..
all I feel is ultimate despair,
tearful eyes are occasional if not rare..

I tried everything to make it work,
they laughed at me and said I was a jerk,
even to think that my efforts would succeed,
I was only as good as a second lead...

looks like mine don't catch the eye,
I do not register, any more than a passer by,
so what if my heart is of gold,
so what if its filled to the rim, with love for him many fold..

and just when I was sulking and sinking so low,
i saw him coming, his face aglow,
the passionate gleam in his eyes, that I always searched,
stopped right in my front why? I wondered..

he was talking to me, I couldn't believe,
I am always in his thoughts and do not leave,
the sparkle of my eyes, is what he can't forget,
if he wont say it now, he will always regret..

I am a person of substance,
its what he liked,
he said i was BEAUTIFUL,
and just then..i BEAUTIFIED....

Social Network Analyser from SAP

almost 3 years ago | Bhargav Gandhi: AGILE SOFTWARE DEVELOPMENT

I was browsing the Business Objects contents on SAP Community Network and came across a cool prototype that SAP is building. It is called as Social Network Analyser Prototype.


The good thing here is that it uses the enterprise data to build a social networking place. It helps by making the corporate relationships quickly visible/available and provides information about your colleagues.

SAP says - 
The purpose of this technology is to deliver an environment where people can easily and quickly analyze corporate network relationships and find the information they are looking for in their daily jobs. The social network analyzer prototype lets you import and aggregate all the business network relationships between people that are already recorded in your business applications, such as:
    • Management hierarchies from your human resources system
    • Data on who worked on which deals, from your sales force automation system
    • Partner, customer, and partner supplier contacts along your supply chain system
    • People who work on similar transactions or projects within your operational systems

I could think of two benefits of having such a tool for an organization

1) I have experienced, being a PMO, sometimes it is very difficult to find out whether we have anyone with a particular skill in our organization, does anyone have prior experience on some problem, etc. Such information is very critical and can affect the way we deliver to our end clients. It is also frustrating that even if there are people in the organization who can help you out, you are simply not able to track them.

Having a Social Network Analyser or similar tool, can help you identify the right person and save a lot of time. Data is always reliable as it is fetched directly from the Business Applications.

2) Wouldn't it be of help if you know your team before you actually begin to work with them ? I think it is a great thing. More the team knows about itself, more productive it would be. It is fun for me to know hobbies, past work experiences, skills of my colleagues which i think improves the team dynamics :).

SAP has made available
demo version of the prototype with a preconfigured dataset. Check it out.

Testing AJAX without a browser with Cucumber and Webrat

almost 3 years ago | Alex Rothenberg: Common Sense Software

I have lately fallen in love with using Cucumber and Webrat for my integration/acceptance testing. Cucumber because it allows non-technical people to write or at least read the test scenarios and Webrat because it matches content and encourages you to write integration tests without relying on xpath to find html elements. The way I like to use these tools is to run Rails integration tests which means its fast since I don’t need to start a mongrel or fire up a browser and can use Rails’ transactional fixtures to rollback all my database changes at the end of each test scenario. The only downside is that you can’t test javascript.

Today I am going to talk about how to get around this and test a form with an ajax autocomplete field. I've built a sample application with all the code examples here and you can download it from http://github.com/alexrothenberg/testing-ajax-example if you like. The application I'm building is just some simple app created with scaffolding that just has a User resource with a name and address. I modified the /users page to not display all users but include the auto_complete typeahead to let you pick a user (imagining there may be a lot) so the page looks something like this.



My first test scenario will ignore the ajax and just test the form which is super easy and can be done by writing a single feature file.

#features/find_a_user.feature
Feature: Allow anyone to find a user and see their details
  In order to handle a large set of users
  I want search with autocomplete
 
  Scenario: View a candidate detail page without testing ajax
    Given "Mickey Mouse" is a user living at "123 Main Street"
    When I am on the homepage
      And I fill in "Which user" with "Mickey Mouse"
      And I press "Find"
    Then I should see "Mickey Mouse"
      And I should see "123 Main Street"



I run it it all passes and I get

$ rake features
(in /Users/alexrothenberg/ruby/testing-ajax-example)
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -I "/Library/Ruby/Gems/1.8/gems/cucumber-0.3.2/lib:lib" 
"/Library/Ruby/Gems/1.8/gems/cucumber-0.3.2/bin/cucumber" --format pretty --require features/step_definitions/user_steps.rb 
--require features/step_definitions/webrat_steps.rb --require features/support/env.rb --require features/support/paths.rb 
features/find_a_user.feature
Feature: Allow anyone to find a user and see their details
  In order to handle a large set of users 
  I want search with autocomplete

  Scenario: View a candidate detail page without testing ajax     # features/find_a_user.feature:5
    Given "Mickey Mouse" is a user living at "123 Main Street"    # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                     # features/step_definitions/webrat_steps.rb:6
    And I fill in "Which user" with "Mickey Mouse"                # features/step_definitions/webrat_steps.rb:22
    And I press "Find"                                            # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                              # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                            # features/step_definitions/webrat_steps.rb:93

1 scenario (1 passed)
6 steps (6 passed)



I makes use of the default webrat steps that cucumber gives you for free in features/steps/webrat_steps.rb so I don’t even have to write any code to get it to pass but I still haven’t tested any of my code that responds to the autocomplete request. I’d like my test to verify that my routes, controller and model will all work together. So, I write another scenario and run it and this time it fails because I haven't defined the typeahead steps for the typeahead lines.

#first scenario omitted

Scenario: View a candidate detail page testing (most of) the ajax # features/find_a_user.feature:13
    Given "Mickey Mouse" is a user living at "123 Main Street"      # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                       # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mickey Mouse"             # features/find_a_user.feature:16
    And I fill in "Which candidate" with the first typeahead result # features/find_a_user.feature:17
    And I press "Find"                                              # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                                # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                              # features/step_definitions/webrat_steps.rb:93

2 scenarios (1 undefined, 1 passed)
13 steps (3 skipped, 2 undefined, 8 passed)

You can implement step definitions for undefined steps with these snippets:

When /^I typeahead in "([^\"]*)" with "([^\"]*)"$/ do |arg1, arg2|
  pending
end

When /^I fill in "([^\"]*)" with the first typeahead result$/ do |arg1|
  pending
end



Now I take the hints cucumber has given me and write my autocomplete steps. The interesting thing here is that I need to leave the response object unchanged so I can fill in the form field after running the typeahead step so I can't use the existing webrat steps as they work on a single pair of request and response objects. So I knew I'd be creating a new class with its own request and response that could be used without affecting the one used by my other cucumber steps. Using good outside-in development practices I deferred thinking about how to do that and first wrote my steps file to look something like this. One interesting thing to notice here is that you can invoke a step from inside another step just by omitting the block as I do in the second step.

When /^I typeahead in "(.*)" with "(.*)"$/ do |field, value|
  field = field_labeled field
  @typeahead = AutoCompleteStepHelper.new(request)
  @typeahead.type(field, value)
end

When /^I fill in "(.*)" with the first typeahead result$/ do |field|
  When %Q[I fill in "#{field}" with "#{@typeahead.items.first}"]
end



Now when run the feature again it fails telling me I haven’t yet built the AutoCompleteStepHelper.

#first scenario omitted

Scenario: View a candidate detail page testing (most of) the ajax # features/find_a_user.feature:13
    Given "Mickey Mouse" is a user living at "123 Main Street"      # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                       # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mickey Mouse"             # features/step_definitions/autocomplete_steps.rb:1
      uninitialized constant AutoCompleteStepHelper (NameError)
      ./features/step_definitions/autocomplete_steps.rb:3:in `/^I typeahead in "(.*)" with "(.*)"$/'
      features/find_a_user.feature:16:in `And I typeahead in "Which user" with "Mickey Mouse"'
    And I fill in "Which candidate" with the first typeahead result # features/step_definitions/autocomplete_steps.rb:7
    And I press "Find"                                              # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                                # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                              # features/step_definitions/webrat_steps.rb:93

2 scenarios (1 failed, 1 passed)
13 steps (1 failed, 4 skipped, 8 passed)



So the next step is to write the AutoCompleteStepHelper class. This class’ job is to have its own request and response that will not affect the ones used by cucumber for the main page requests. It turns out that I can do this by having my class extend ActionController::IntegrationTest and I can even use webrat methods in it because webrat adds its methods to IntegrationTest. In this example I'm calling visit and current_dom and using nokogiri to parse the dom. It is a little weird that I'm subclassing IntegrationTest but this class is not a TestUnit class itself but I decided that was okay.

class AutoCompleteStepsHelper < ActionController::IntegrationTest
  def initialize(existing_request)
    @controller_name = existing_request.parameters[:controller]
    @controller_class = "#{@controller_name.to_s.camelize}Controller".constantize
    raise "Can't determine controller class for #{@controller_class_name}" if @controller_class.nil?

    @controller = @controller_class.new
    @request = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new
    @response.session = @request.session
  end
 
  def type(field, value)
    visit url_for(:controller=>@controller_name, :action=>"auto_complete_for_#{field.id}", field.send(:name)=>value)
  end
 
  def items
    current_dom.search('//ul/li').map(&:inner_html)
  end
end



Now when I run the feature it all passes.

#first scenario omitted

  Scenario: View a candidate detail page testing (most of) the ajax # features/find_a_user.feature:13
    Given "Mickey Mouse" is a user living at "123 Main Street"      # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                       # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mickey Mouse"             # features/step_definitions/autocomplete_steps.rb:1
    And I fill in "Which user" with the first typeahead result      # features/step_definitions/autocomplete_steps.rb:7
    And I press "Find"                                              # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                                # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                              # features/step_definitions/webrat_steps.rb:93

2 scenarios (2 passed)
13 steps (13 passed)



The last step I took was to test that the list returned in the typeahead was correct. I created another scenario in my feature.

Scenario: Typeahead should return 2 users that match but not a third
    Given "Mickey Mouse" is a user living at "123 Main Street"
      And "Donald Duck" is a user living at "123 Pond Lane"
      And "Minnie Mouse" is a user living at "123 Disney Avenue"
    When I am on the homepage
      And I typeahead in "Which user" with "Mi"
    Then I should see in my typeahead "Mickey Mouse"
      And I should see in my typeahead "Minnie Mouse"
      And I should not see in my typeahead "Donald Duck"



and I added two new steps

Then /^I should see in my typeahead "(.*)"$/ do |text|
  @typeahead.response_body.should =~ /#{text}/m
end

Then /^I should not see in my typeahead "(.*)"$/ do |text|
  @typeahead.response_body.should_not =~ /#{text}/m
end



Now when I run my features all 3 scenarios are passing

$ rake features
(in /Users/alexrothenberg/ruby/testing-ajax-example)
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -I "/Library/Ruby/Gems/1.8/gems/cucumber-0.3.2/lib:lib" 
"/Library/Ruby/Gems/1.8/gems/cucumber-0.3.2/bin/cucumber" --format pretty --require features/step_definitions/autocomplete_steps.rb 
--require features/step_definitions/user_steps.rb --require features/step_definitions/webrat_steps.rb 
--require features/support/autocomplete_steps_helper.rb --require features/support/env.rb --require features/support/paths.rb 
features/find_a_user.feature

Feature: Allow anyone to find a user and see their details
  In order to handle a large set of users 
  I want search with autocomplete

  Scenario: View a candidate detail page without testing ajax  # features/find_a_user.feature:5
    Given "Mickey Mouse" is a user living at "123 Main Street" # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                  # features/step_definitions/webrat_steps.rb:6
    And I fill in "Which user" with "Mickey Mouse"             # features/step_definitions/webrat_steps.rb:22
    And I press "Find"                                         # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                           # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                         # features/step_definitions/webrat_steps.rb:93

  Scenario: View a candidate detail page testing (most of) the ajax # features/find_a_user.feature:13
    Given "Mickey Mouse" is a user living at "123 Main Street"      # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                       # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mickey Mouse"             # features/step_definitions/autocomplete_steps.rb:1
    And I fill in "Which user" with the first typeahead result      # features/step_definitions/autocomplete_steps.rb:7
    And I press "Find"                                              # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                                # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                              # features/step_definitions/webrat_steps.rb:93

  Scenario: Typeahead should return 2 users that match but not a third # features/find_a_user.feature:22
    Given "Mickey Mouse" is a user living at "123 Main Street"         # features/step_definitions/user_steps.rb:1
    And "Donald Duck" is a user living at "123 Pond Lane"              # features/step_definitions/user_steps.rb:1
    And "Minnie Mouse" is a user living at "123 Disney Avenue"         # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                          # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mi"                          # features/step_definitions/autocomplete_steps.rb:1
    Then I should see in my typeahead "Mickey Mouse"                   # features/step_definitions/autocomplete_steps.rb:16
    And I should see in my typeahead "Minnie Mouse"                    # features/step_definitions/autocomplete_steps.rb:16
    And I should not see in my typeahead "Donald Duck"                 # features/step_definitions/autocomplete_steps.rb:20

3 scenarios (3 passed)
21 steps (21 passed)



What I've done is not full javascript testing (for that I'm planning to look into Blue-Ridge from Relevance). This technique does allow you to test ajax (skipping the "J") without a browser.

Testing AJAX without a browser with Cucumber and Webrat

almost 3 years ago | Alex Rothenberg: Common Sense Software

I have lately fallen in love with using Cucumber and Webrat for my integration/acceptance testing. Cucumber because it allows non-technical people to write or at least read the test scenarios and Webrat because it matches content and encourages you to write integration tests without relying on xpath to find html elements. The way I like to use these tools is to run Rails integration tests which means its fast since I don’t need to start a mongrel or fire up a browser and can use Rails’ transactional fixtures to rollback all my database changes at the end of each test scenario. The only downside is that you can’t test javascript.

Today I am going to talk about how to get around this and test a form with an ajax autocomplete field. I've built a sample application with all the code examples here and you can download it from http://github.com/alexrothenberg/testing-ajax-example if you like. The application I'm building is just some simple app created with scaffolding that just has a User resource with a name and address. I modified the /users page to not display all users but include the auto_complete typeahead to let you pick a user (imagining there may be a lot) so the page looks something like this.



My first test scenario will ignore the ajax and just test the form which is super easy and can be done by writing a single feature file.

#features/find_a_user.feature
Feature: Allow anyone to find a user and see their details
  In order to handle a large set of users
  I want search with autocomplete
 
  Scenario: View a candidate detail page without testing ajax
    Given "Mickey Mouse" is a user living at "123 Main Street"
    When I am on the homepage
      And I fill in "Which user" with "Mickey Mouse"
      And I press "Find"
    Then I should see "Mickey Mouse"
      And I should see "123 Main Street"



I run it it all passes and I get

$ rake features
(in /Users/alexrothenberg/ruby/testing-ajax-example)
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -I "/Library/Ruby/Gems/1.8/gems/cucumber-0.3.2/lib:lib" 
"/Library/Ruby/Gems/1.8/gems/cucumber-0.3.2/bin/cucumber" --format pretty --require features/step_definitions/user_steps.rb 
--require features/step_definitions/webrat_steps.rb --require features/support/env.rb --require features/support/paths.rb 
features/find_a_user.feature
Feature: Allow anyone to find a user and see their details
  In order to handle a large set of users 
  I want search with autocomplete

  Scenario: View a candidate detail page without testing ajax     # features/find_a_user.feature:5
    Given "Mickey Mouse" is a user living at "123 Main Street"    # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                     # features/step_definitions/webrat_steps.rb:6
    And I fill in "Which user" with "Mickey Mouse"                # features/step_definitions/webrat_steps.rb:22
    And I press "Find"                                            # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                              # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                            # features/step_definitions/webrat_steps.rb:93

1 scenario (1 passed)
6 steps (6 passed)



I makes use of the default webrat steps that cucumber gives you for free in features/steps/webrat_steps.rb so I don’t even have to write any code to get it to pass but I still haven’t tested any of my code that responds to the autocomplete request. I’d like my test to verify that my routes, controller and model will all work together. So, I write another scenario and run it and this time it fails because I haven't defined the typeahead steps for the typeahead lines.

#first scenario omitted

Scenario: View a candidate detail page testing (most of) the ajax # features/find_a_user.feature:13
    Given "Mickey Mouse" is a user living at "123 Main Street"      # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                       # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mickey Mouse"             # features/find_a_user.feature:16
    And I fill in "Which candidate" with the first typeahead result # features/find_a_user.feature:17
    And I press "Find"                                              # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                                # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                              # features/step_definitions/webrat_steps.rb:93

2 scenarios (1 undefined, 1 passed)
13 steps (3 skipped, 2 undefined, 8 passed)

You can implement step definitions for undefined steps with these snippets:

When /^I typeahead in "([^\"]*)" with "([^\"]*)"$/ do |arg1, arg2|
  pending
end

When /^I fill in "([^\"]*)" with the first typeahead result$/ do |arg1|
  pending
end



Now I take the hints cucumber has given me and write my autocomplete steps. The interesting thing here is that I need to leave the response object unchanged so I can fill in the form field after running the typeahead step so I can't use the existing webrat steps as they work on a single pair of request and response objects. So I knew I'd be creating a new class with its own request and response that could be used without affecting the one used by my other cucumber steps. Using good outside-in development practices I deferred thinking about how to do that and first wrote my steps file to look something like this. One interesting thing to notice here is that you can invoke a step from inside another step just by omitting the block as I do in the second step.

When /^I typeahead in "(.*)" with "(.*)"$/ do |field, value|
  field = field_labeled field
  @typeahead = AutoCompleteStepHelper.new(request)
  @typeahead.type(field, value)
end

When /^I fill in "(.*)" with the first typeahead result$/ do |field|
  When %Q[I fill in "#{field}" with "#{@typeahead.items.first}"]
end



Now when run the feature again it fails telling me I haven’t yet built the AutoCompleteStepHelper.

#first scenario omitted

Scenario: View a candidate detail page testing (most of) the ajax # features/find_a_user.feature:13
    Given "Mickey Mouse" is a user living at "123 Main Street"      # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                       # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mickey Mouse"             # features/step_definitions/autocomplete_steps.rb:1
      uninitialized constant AutoCompleteStepHelper (NameError)
      ./features/step_definitions/autocomplete_steps.rb:3:in `/^I typeahead in "(.*)" with "(.*)"$/'
      features/find_a_user.feature:16:in `And I typeahead in "Which user" with "Mickey Mouse"'
    And I fill in "Which candidate" with the first typeahead result # features/step_definitions/autocomplete_steps.rb:7
    And I press "Find"                                              # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                                # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                              # features/step_definitions/webrat_steps.rb:93

2 scenarios (1 failed, 1 passed)
13 steps (1 failed, 4 skipped, 8 passed)



So the next step is to write the AutoCompleteStepHelper class. This class’ job is to have its own request and response that will not affect the ones used by cucumber for the main page requests. It turns out that I can do this by having my class extend ActionController::IntegrationTest and I can even use webrat methods in it because webrat adds its methods to IntegrationTest. In this example I'm calling visit and current_dom and using nokogiri to parse the dom. It is a little weird that I'm subclassing IntegrationTest but this class is not a TestUnit class itself but I decided that was okay.

class AutoCompleteStepsHelper < ActionController::IntegrationTest
  def initialize(existing_request)
    @controller_name = existing_request.parameters[:controller]
    @controller_class = "#{@controller_name.to_s.camelize}Controller".constantize
    raise "Can't determine controller class for #{@controller_class_name}" if @controller_class.nil?

    @controller = @controller_class.new
    @request = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new
    @response.session = @request.session
  end
 
  def type(field, value)
    visit url_for(:controller=>@controller_name, :action=>"auto_complete_for_#{field.id}", field.send(:name)=>value)
  end
 
  def items
    current_dom.search('//ul/li').map(&:inner_html)
  end
end



Now when I run the feature it all passes.

#first scenario omitted

  Scenario: View a candidate detail page testing (most of) the ajax # features/find_a_user.feature:13
    Given "Mickey Mouse" is a user living at "123 Main Street"      # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                       # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mickey Mouse"             # features/step_definitions/autocomplete_steps.rb:1
    And I fill in "Which user" with the first typeahead result      # features/step_definitions/autocomplete_steps.rb:7
    And I press "Find"                                              # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                                # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                              # features/step_definitions/webrat_steps.rb:93

2 scenarios (2 passed)
13 steps (13 passed)



The last step I took was to test that the list returned in the typeahead was correct. I created another scenario in my feature.

Scenario: Typeahead should return 2 users that match but not a third
    Given "Mickey Mouse" is a user living at "123 Main Street"
      And "Donald Duck" is a user living at "123 Pond Lane"
      And "Minnie Mouse" is a user living at "123 Disney Avenue"
    When I am on the homepage
      And I typeahead in "Which user" with "Mi"
    Then I should see in my typeahead "Mickey Mouse"
      And I should see in my typeahead "Minnie Mouse"
      And I should not see in my typeahead "Donald Duck"



and I added two new steps

Then /^I should see in my typeahead "(.*)"$/ do |text|
  @typeahead.response_body.should =~ /#{text}/m
end

Then /^I should not see in my typeahead "(.*)"$/ do |text|
  @typeahead.response_body.should_not =~ /#{text}/m
end



Now when I run my features all 3 scenarios are passing

$ rake features
(in /Users/alexrothenberg/ruby/testing-ajax-example)
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -I "/Library/Ruby/Gems/1.8/gems/cucumber-0.3.2/lib:lib" 
"/Library/Ruby/Gems/1.8/gems/cucumber-0.3.2/bin/cucumber" --format pretty --require features/step_definitions/autocomplete_steps.rb 
--require features/step_definitions/user_steps.rb --require features/step_definitions/webrat_steps.rb 
--require features/support/autocomplete_steps_helper.rb --require features/support/env.rb --require features/support/paths.rb 
features/find_a_user.feature

Feature: Allow anyone to find a user and see their details
  In order to handle a large set of users 
  I want search with autocomplete

  Scenario: View a candidate detail page without testing ajax  # features/find_a_user.feature:5
    Given "Mickey Mouse" is a user living at "123 Main Street" # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                  # features/step_definitions/webrat_steps.rb:6
    And I fill in "Which user" with "Mickey Mouse"             # features/step_definitions/webrat_steps.rb:22
    And I press "Find"                                         # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                           # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                         # features/step_definitions/webrat_steps.rb:93

  Scenario: View a candidate detail page testing (most of) the ajax # features/find_a_user.feature:13
    Given "Mickey Mouse" is a user living at "123 Main Street"      # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                       # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mickey Mouse"             # features/step_definitions/autocomplete_steps.rb:1
    And I fill in "Which user" with the first typeahead result      # features/step_definitions/autocomplete_steps.rb:7
    And I press "Find"                                              # features/step_definitions/webrat_steps.rb:14
    Then I should see "Mickey Mouse"                                # features/step_definitions/webrat_steps.rb:93
    And I should see "123 Main Street"                              # features/step_definitions/webrat_steps.rb:93

  Scenario: Typeahead should return 2 users that match but not a third # features/find_a_user.feature:22
    Given "Mickey Mouse" is a user living at "123 Main Street"         # features/step_definitions/user_steps.rb:1
    And "Donald Duck" is a user living at "123 Pond Lane"              # features/step_definitions/user_steps.rb:1
    And "Minnie Mouse" is a user living at "123 Disney Avenue"         # features/step_definitions/user_steps.rb:1
    When I am on the homepage                                          # features/step_definitions/webrat_steps.rb:6
    And I typeahead in "Which user" with "Mi"                          # features/step_definitions/autocomplete_steps.rb:1
    Then I should see in my typeahead "Mickey Mouse"                   # features/step_definitions/autocomplete_steps.rb:16
    And I should see in my typeahead "Minnie Mouse"                    # features/step_definitions/autocomplete_steps.rb:16
    And I should not see in my typeahead "Donald Duck"                 # features/step_definitions/autocomplete_steps.rb:20

3 scenarios (3 passed)
21 steps (21 passed)



What I've done is not full javascript testing (for that I'm planning to look into Blue-Ridge from Relevance). This technique does allow you to test ajax (skipping the "J") without a browser.