testing a multi-step workflow in rspec - ruby

I'd like to know about idioms or best practices for testing a multi-step workflow using rspec.
Let's take as an example a "shopping cart" system, where the buying process might be
when user submits to basket and we are not using https, redirect to https
when user submits to basket and we are using https and there is no cookie, create and display a new basket and send back a cookie
when user submits to basket and we are using https and there is a valid cookie and the new item is for a different product than the first item, add a line to the basket and display both lines
when user submits to basket and we are using https and there is a valid cookie and the new item is for the same product as a previous one, increment that basket line's quantity and display both lines
when user clicks 'checkout' on the basket page and is using https and there is a cookie and the basket is non-empty and ...
...
I've read http://eggsonbread.com/2010/03/28/my-rspec-best-practices-and-tips/ which advises i.a that each "it block" should contain only one assertion: instead of doing the computation and then testing several attributes in the same block, use a "before" inside a context to create (or retrieve) the object under test and assign it to #some_instance_variable, then write each attribute test as a separate block. That helps a little, but in a case such as outlined above where testing step n requires doing all the setup for steps [1..n-1] I find myself either duplicating setup code (obviously not good) or creating lots of helper functions with increasingly unwieldy names (def create_basket_with_three_lines_and_two_products) and calling them consecutively in each step's before block.
Any tips on how to do this less verbosely/tediously? I appreciate the general principle behind the idea that each example should not depend on state left behind by previous examples, but when you're testing a multi-step process and things can go wrong at any step, setting up the context for each step is inevitably going to require rerunning all the setup for the previous n steps, so ...

Here's one possible approach -- define an object that creates the necessary state for each step and pass it forward for each successive one. Basically you need to mock/stub the method calls for all the setup conditions:
class MultiStep
def initialize(context)
#context = context
end
def init_vars
#cut = #context.instance_variable_get(:#cut)
end
def setup(step)
init_vars
method(step).call
end
def step1
#cut.stub(:foo).and_return("bar")
end
def step2
step1
#cut.stub(:foo_bar).and_return("baz_baz")
end
end
class Cut # Class Under Test
def foo
"foo"
end
def foo_bar
"foo_bar"
end
end
describe "multiple steps" do
before(:each) do
#multi_stepper = MultiStep.new(self)
#cut = Cut.new
end
it "should setup step1" do
#multi_stepper.setup(:step1)
#cut.foo.should == "bar"
#cut.foo_bar.should == "foo_bar"
end
it "should setup step2" do
#multi_stepper.setup(:step2)
#cut.foo.should == "bar"
#cut.foo_bar.should == "baz_baz"
end
end

Certainly too late for OP, but this could be handy for others - the rspec-steps gem seems to be built for this exact situation: https://github.com/LRDesign/rspec-steps
It might be worthwhile to look at https://github.com/railsware/rspec-example_steps and https://github.com/jimweirich/rspec-given as well. I settled on rspec-steps, but I was in a rush and these other options might actually be better for all I know.

Related

How mocking works mocha gem?

I am new to mocha gem before that I am using minitest to test my product. Then I came across a situation where my application is publishing jobs to facebook. It selects some jobs and then publish them on facebook.
So somebody told me to use mocking and i found mocha gem.
I see a sample test.
def test_mocking_an_instance_method_on_a_real_object
job = Job.new
job.expects(:save).returns(true)
assert job.save
end
But I did not get the idea. In my jobs controller, I have validations
and the empty job cannot be saved successfully. But here with mocking the
above test assert that job can be saved without mandatory fields.So what exactly we test in above test case?
It is a good practice generally for several reasons:
From an isolation point of view: The responsibility of the controller is to handle the incoming request and trigger actions accordingly. In our given case the actions are: create a new Job, and issue a new post to Facebook if everything fits. (Please notice our controller doesn't need to know about how to post to FB)
So imagine the following controller action:
def create
job = Job.new job_params
if job.save
FacebookService.post_job job
...
else
...
end
end
I would test it like:
class JobsControllerTest < ActionController::TestCase
test "should create a job and issue new FB post" do
job_params = { title: "Job title" }
# We expect the post_job method will be called on the FacebookService class or module, and we replace the original implementation with an 'empty/mock' method that does nothing
FacebookService.expects :post_job
post :create, job_params
assert_equal(Job.count, 1) # or similar
assert_response :created
end
end
The other advantage is: FacebookService.post_job might take significant time, and might require internet access etc, we don't want our tests to pending on those, especially if we have CI.
And finally I would test the real FB posting in the FacebookService test, and maybe stub out some other method, to prevent posting on FB every single time when the test runs (it needs time, internet, FB account...).

checking groups at runtime with devise and devise_ldap_authenticatable

I can get this devise_ldap_authenticatable working just fine when I don't care about what groups they are, it either connects to ldap and authenticates the user signing in under devise or doesn't. But I want to let only certain members that are apart of one or several specific groups in. I had a post on this question here:
Checking group membership in rails devise ldap gem, is it in the yaml?
(the gem for completeness sake is this one: https://github.com/cschiewek/devise_ldap_authenticatable)
Got to thinking I am asking the wrong question. I think I want to know how in devise (and the devise_ldap_authenticatable is the data stored where perhaps I can peek at my array of memberOf's myself and check the groups for myself in code, and then at that time don't let them in. Is there anywhere on the net that's hows this? My googling has turned up nothing but not being a ldap or devise pro I am guessing my terms suck.
I am sure I just might of missed the how to do this, closest I can see that might help (Though in its form as I read it makes little sense to me is the part on the readme here:
https://github.com/cschiewek/devise_ldap_authenticatable/blob/master/README.md
about querying ldap, is this the case?)
You could do this with a callback or validation on the User (or equivalent) model.
before_create :user_is_not_member_of_specified_group?
private
def user_is_not_member_of_specified_group?
member_of = Devise::LdapAdapter.get_ldap_param(self.username,"memberOf")
test member_of
end
where test is a method that returns true/false based on your conditions for the member groups.
The Devise::LdapAdapter.get_ldap_param(self.username,"memberOf") is a method from devise_ldap_authenticatable that will return an array of member groups. You'll want to run your group testing on this array.
If you use a validation you could specify an error message for users that failed the test. Hope this helps.
EDIT
Another way to handle this would be to let your gem handle the redirection and error messages by monkeypatching the authorized? method in Devise::LdapAdapter::LdapConnect (https://github.com/cschiewek/devise_ldap_authenticatable/blob/master/lib/devise_ldap_authenticatable/ldap_adapter.rb). It would look like:
Devise::LdapAdapter::LdapConnect.class_eval do
def user_group_test
member_of = self.ldap_param_value("memberOf")
test member_of # your group test method
end
def authorized?
DeviseLdapAuthenticatable::Logger.send("Authorizing user #{dn}")
if !user_group_test
DeviseLdapAuthenticatable::Logger.send("Not authorized because custom authentication failed.")
return false
elsif !authenticated?
DeviseLdapAuthenticatable::Logger.send("Not authorized because not authenticated.")
return false
elsif !in_required_groups?
DeviseLdapAuthenticatable::Logger.send("Not authorized because not in required groups.")
return false
elsif !has_required_attribute?
DeviseLdapAuthenticatable::Logger.send("Not authorized because does not have required attribute.")
return false
else
return true
end
end
end
You would want to put this in a custom initializer file in config/initializers.

Page objects in watir splash -- how to ensure that the proper page object is instantiated

I am using the Watir Splash framework to test a web application, and I have setup two page classes. The first is the "Login" page which is detailed here:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login_btn
modify button(:id => 'btnLogin'), :click => lambda {redirect_to VehicleSelection}
end
The other page class is the "Vehicle Selection" page. I have used the modify method as shown in the documentation here to ensure that the vehicle selection page object is available for RSpec after a successful login.
But what happens if the login failed? I have some test cases that deliberately feed incorrect information into the login form to ensure that the authentication is working properly. RSpec would need the methods defined in the "Login" class to access the correct elements to complete the test case. In this case, the way that I have specified the method a "VehicleSeleciton" object will be returned regardless. (or so it appears)
Any help is appreciated. Also, I'm open to other suggestions for testing frameworks, especially if there is more example code for me to reference.
Below are a couple of approaches I have tried. I was not using the WatirSplash framework, but the same concepts applied (though the attempted WatirSplash example code might not be 100% accurate).
Solution 1: Do return page objects
My personal preference is to not have page objects returning page objects. Instead, I find it easier to read/work with explicit initializations of each page object within the test. Alister Scott discussed this in his blog.
Your tests would then look like:
#For login successful tests
page = App::Page::Login.new
page.login_btn.click
page = App::Page::VehicleSelection.new #The VehicleSelection page is explicitly initialized
page.validate_page #or whatever you want to do with the page
#For login failed tests
page = App::Page::Login.new
page.login_btn.click
page.validate_page #or whatever you want to do with the page
Solution 2: Create multiple methods for login
Another solution, would be to create two login methods - one for successful login and one for unsuccessful login.
The page object could be:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login(user, password)
#Do whatever code to input name and password and then click the button
#Then redirect to the VehicleSelection page since that is where you will want to go most often
redirect_to VehicleSelection
end
def login_failed(user, password)
login(user, password)
#Return the Login page (instead of the VehicleSelection page).
redirect_to Login
end
end
end
end
With the tests being:
#For login successful tests
login_page = App::Page::Login.new
vehicle_page = login_page.login(user, password)
vehicle_page.validate_page #or whatever you want to do with the Vehicle Selection page
#For login failed tests
login_page = App::Page::Login.new
login_page.login_failed(user, password)
login_page.validate_page #or whatever you want to do with the Login page
Solution 3: Make the button know where it is going
Another solution, would be to have the login button know which page to redirect to.
The page object could be:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login_btn(login_successful=true)
if login_successful
modify button(:id => 'btnLogin'), :click => lambda {redirect_to VehicleSelection}
else
modify button(:id => 'btnLogin'), :click => lambda {redirect_to Login}
end
end
end
end
end
With the tests being:
#For login successful tests
login_page= App::Page::Login.new
vehicle_page = login_page.login_btn.click
vehicle_page.validate_page #or whatever you want to do with the Vehicle Selection page
#For login failed tests
login_page= App::Page::Login.new
login_page.login_btn(false).click
login_page.validate_page #or whatever you want to do with the Login page
Thanks for trying out my gem WatirSplash. I would have written something in the lines of solution #2 - e.g. create two separate methods for successful login and failed login. Using #modify is not needed in either method, like Justin did.
Also, i'd suggest you to use my other gem test-page instead, which is more or less the same, as Page Objects in WatirSplash, but it is extracted into separate gem - WatirSplash will be deprecated in the long term due to all of its parts being exctracted into separate gems giving better control of which functionality is needed in each project.

How much should interfaces of elements in Page Objects be abstracted?

I have a page object called LineItemsPage
class LineItemsPage
attr_accessor :add_line_item_button
def initialize(test_env)
#browser = test_env[:browser]
#action_bar = #browser.div(:id => 'lineitems_win').div(:class => 'window-body').div(:class => 'actionbar')
#add_line_item_button = #action_bar.img(:class => 'button add')
end
def method_missing(sym, *args, &block)
#browser.send sym, *args, &block
end
end
I use it like so:
When /^I click on Add Item and enter the following values:$/ do |table|
#line_items_page = LineItemsPage.new(#test_env)
#line_items_page.add_line_item_button.when_present.click
end
I'm wondering if I should be abstracting the click, by adding something like the following to my LineItemsPage class:
def add_item
self.add_line_item_button.when_present.click
end
And then using it like so:
#line_items_page.add_item
I'm looking for best practices, either with regards to Page Object in particular or Ruby in general. I feel that encapsulating the interface by using add_item() is going a bit far, but I'm wondering if I'm unaware of issues I might run into down the road if I don't do that.
Personally, I try to make my page object methods be in the domain language with no reference to the implementation.
I used to do something like #line_items_page.add_line_item_button.when_present.click, however it has caused problems in the following scenarios:
1) The add line item was changed from a button to a link.
2) The process for adding a line item has changed - say its now done by a right-click or it has become a two step process (like open some dropdown and then click the add line).
In either case, you would have to locate all the places you add line items and update them. If you had all the logic in the add_item page object method, you would only have to update the one place.
From an implementation perspective, I have found that Cheezy's page object accessors work pretty well. However, for image buttons (or any of your app's custom controls), I would add additional methods to the PageObject::Accessors module. Or if they are one off controls, you can add the methods directly to the specific page object.
Update - Reply to Comment Regarding Some Starting Points:
I have not come across too much documentation, but here are a couple links that might help:
1) The Cheezy Page Object project wiki - Gives a simple example to get started
2) Cheezy's blog posts where the page object gem first started. Note that the content here might not be exactly how the gem is currently implemented, but I think it gives a good foundation to understanding what he is trying to achieve. This in turn makes it easier to understand what is happening when you have to open up and modify the gem to fit you needs.

Reuse Cucumber steps

I want to reuse some Cucumber steps but can't seem to find the right way.
I want to write a step like:
Given /^I login with (.*) credentials$/ |type|
# do stuff with type being one of "invalid" or "valid"
end
But then have another step like:
Given /^I login successfully$
# call "Given I login with valid credentials"
end
So in testing user authentication I can use the former, but most other places, I can use the latter, and not actually have to repro code.
Is there a way to call that other step, or do I just put the logic in a helper method, and call said method from each task (basically a method extraction refactoring, which, after reading my question makes me believe that's actually the best way anyway)?
Note that the method for calling steps within steps has changed in recent versions of cucumber, which you'll see if you get an error like "WARNING: Using 'Given/When/Then' in step definitions is deprecated, use 'step' to call other steps instead:/path/to/step_definitions/foo_steps.rb:631:in `block in '
". See the cucumber wiki for details.
The gist of the change is that you should now use the step or steps methods.
When /^I make all my stuff shiny$/
step "I polish my first thing"
end
When /^I make all my stuff shiny$/
steps %Q{
When I polish my first thing
When I shine my second thing
}
end
UPDATE: The method described below has been deprecated. The recommended way to call a step from within another step now looks like this:
Given /^I login successfully$/
step "I login with valid credentials"
end
Old, deprecated method (for reference):
You can call steps from other steps like this:
Given /^I login successfully$/
Given "I login with valid credentials"
Then "I should be logged in"
end
If all of the scenarios within a feature require this (or other steps), you can also add a Background to each features, with the common steps, like so:
Background:
Given I log in with valid credentials
Scenario: Change my password
Given I am on the account page
Calling steps from step definitions is a bad practice and has some disadvantages:
If scenario will fail and there are nested step invocations, you will get only the last invoked step definition in the stack trace. It may be hard to find from which place that last stepdef was called
Call to stepdef is sometimes harder to find and read than ruby method
Ruby methods give you more power than calling steps from step defs
Aslak Hellesøy recommends to extract popular actions to World instead of reusing steps. It isolates those actions in one place, makes this code easier to find. You can extract code to usual Ruby classes or modules as well.
#/support/world_extensions.rb
module KnowsUser
def login
visit('/login')
fill_in('User name', with: user.name)
fill_in('Password', with: user.password)
click_button('Log in')
end
def user
#user ||= User.create!(:name => 'Aslak', :password => 'xyz')
end
end
World(KnowsUser)
#/step_definitions/authentication_steps.rb
When /^I login$/ do
login
end
Given /^a logged in user$/ do
login
end
Here is a useful discussion on the subject in Cucumber mailing list - link
Best wrap your steps in %{} rather than quotes. Then, you don't need to escape double quotes which you'll need to use frequently.:
Given /^I login successfully$
step %{I login with valid credentials}
end
Given /^I login with (.*) credentials$/ |type|
# do stuff with type being one of "invalid" or "valid"
end
Reuse keywords in feature file which will provide code reusability.
It is highly NOT recommended to call step defs within step defs.
I would write my feature file this way,
Scenario Outline: To check login functionality
Given I login with "<username>" and "<password>"
Then I "<may or may not>" login successfully
Examples:
|username|password|may or may not|
|paul |123$ |may |
|dave |1111 |may not |
In my step definition, (This is Java)
#Given(I login with \"([^\"]*)\" and \"([^\"]*)\"$)
public void I_login_with_and(String username, String password){
//login with username and password
}
#Then(I \"([^\"]*)\" login successfully$)
public void I_login_successully_if(String validity){
if(validity.equals("may")){
//assert for valid login
}
else
if(validity.equals("may not")){
//assert for invalid login
}
}
In this way, there is a lot of code reusability.
Your same Given and Then handles both valid and invalid scenarios.
At the same time, your feature file makes sense to the readers.

Resources