Cucumber multi-session
This article is indirect translation of Cucumber – obsluga kilku sesji – polish version (from andrzejsliwa.com devblog).
Most of standard testing related tasks can be achieved in a simple way using the default steps from Cucumber. By assumption Cucumber is for functional testing, but it can also be used to implement integration test. This is especially useful when we want test interaction between users, when they should get on-line notifications – and this should be tested without login/logout functionality, like in shout box which was popular some time ago.
When using integration tests you could use block open_session:
def login(user) open_session do |sess| sess.extend(CustomDsl) u = users(user) sess.https! sess.post "/login", :username => u.username, :password => u.password assert_equal '/welcome', path sess.https!(false) end end
But in the case of cucumber which is based on the steps it was necessary to find a solution that is suited to the form in which scenarios are created.
So I have asked my friend Andrzej Sliwa to write an example code, this is the example:
module ActionController module Integration class Session def switch_session_by_name(name) if @sessions_by_name.nil? @sessions_by_name = { :default => @response.session.clone } end @sessions_by_name[name.to_sym] ||= @sessions_by_name[:default].clone @response.session = @sessions_by_name[name.to_sym] end end end end Given /^session name is "([^\"]*)"$/ do |name| switch_session_by_name(name) end
Use of this mechanism is trivially easy to perform the following step:
Given session name is "guest no 1 session"
g
In this case, a named session is created which is not dependent on others (including the default). Access to default session is called by using the default name:
Given session name is "default"
You can always get back to named session using the same name as before, the state of it will be persisted for you.
More details on Integration tests: http://guides.rubyonrails.org/testing.html#integration-testing-examples
After digging a little deeper, I found a solution that seems to work much better. The last post had some bugs, particularly related to switching sessions right after requesting a form and then expecting to get back to the form after the second session is complete (i.e. to test opportunistic locking). The original solution swapped out ActionController::IntegrationTest, but didn’t handle ActionController::Integration::Session. This solution does, though hopefully someone might be able to find a more clean and elegant solution.
module ActionController
module Integration
module Runner
def switch_session_by_name(name)
if @sessions_by_name.nil?
# Stash the original session for later
@sessions_by_name = {
:default => {
:session => @integration_session,
:webrat_session => @integration_session.delegate.webrat_session,
:webrat_adapter => @integration_session.delegate.webrat_session.adapter
}
}
end
if @sessions_by_name[name.to_sym].nil?
# if the session doesn’t exist, create a new session environment
@sessions_by_name[name.to_sym] = {
:session => open_session,
:webrat_session => Webrat::Session.new,
:webrat_adapter => Webrat.adapter_class.new(@integration_session.delegate)
}
end
@integration_session = @sessions_by_name[name.to_sym][:session]
@integration_session.delegate.webrat_session = @sessions_by_name[name.to_sym][:webrat_session]
@integration_session.delegate.webrat_session.adapter = @sessions_by_name[name.to_sym][:webrat_adapter]
@integration_session.delegate.instance_eval do
def current_user
::User.find(self.webrat_session.adapter.integration_session.session[:user_id])
end
end
end
end
end
end
Given /^session name is “([^\”]*)”$/ do |name|
switch_session_by_name(name)
end
I found that this didn’t seem to do the trick when dealing with webrat. The current_user stayed the same in my controllers and Cucumber::Rails::World.
This is the first time I’ve dug deep into cucumber and webrat, but I was able to use this ugly bit of code to generate a new webrat session and swap them out. The only thing I can’t seem to do is get Cucumber::Rails::World#session to have the correct value (i.e. when in the step “And I want to debug”), though everything is correct in the actual steps and controllers. Hopefully someone can clean this up some.
Although it shouldn’t matter, I’m using rails 2.3.5, cucumber 0.8.5, cucumber-rails 0.3.2, webrat 0.7.1, & thoughtbot-clearance 0.6.9.
module ActionController
class IntegrationTest
def switch_webrat_session_by_name(name)
if @sessions_by_name.nil?
# Stash the original session for later
@sessions_by_name = { :default => self.webrat_session}
end
if @sessions_by_name[name.to_sym].nil?
# if the session doesn’t exist, create a new webrat session environment
self.webrat_session = Webrat::Session.new
self.webrat_session.adapter = Webrat.adapter_class.new(Cucumber::Rails::World.new)
webrat_session=self.webrat_session
@sessions_by_name[name.to_sym] = webrat_session
else
# Restore existing webrat session environment
@_webrat_session = @sessions_by_name[name.to_sym]
webrat_session=self.webrat_session
end
# Fix Cucumber::Rails::World object when in step “I want to debug” to report the correct current_user
self.instance_eval do
def current_user
::User.find(self.webrat_session.adapter.integration_session.session[:user_id])
end
end
end
end
end
Given /^session name is “([^\”]*)”$/ do |name|
switch_webrat_session_by_name(name)
end
Hello, I am trying your solution but it doesn’t work: I put the ActionController module stuff in a file and I require it in cucumber, but later when I call the switch_session_by_name method I get a “undefined method `session’ for nil:NilClass” error, it seems @response is nil when the method is called… am I missing something?
Thanks