Cucumber and Clearance: current_user in steps - ruby-on-rails

What I what to accomplish is to use (rely on) current_user method while defining Cucumber steps. I'm using Clearance in my project.
First of all I tried to use sign_in but it didn't work (I guess Cucumber World doesn't know about Clearance methods...).
So how do I make Cuckes recognize current_user and sign_in/sign_out methods?

Your Cucumber features should be driving your application through the public user interface. Something like:
Given /^I am signed in as "([^\"]*)"%/ do |username|
visit 'sign_in'
fill_in 'Username', :with => username
click 'Sign In'
end
Since the current_user method isn't available to the browser, you shouldn't be using it in your spec.
You could fake it in your steps by storing #current_user in the above step and then providing an attribute reader for it.

I disagree with the idea that every acceptance test (cucumber or otherwise) must exercise the login logic. Luckily, if you agree, Clearance has added a back door in tests that lets you skip the sign in steps.
user = create(:user)
visit posts_path(as: user)
Now you can leave your login-related features driving the login ui as a user would and skip that for features that aren't directly related to logging in.

Related

Writing Cucumber tests that pass extra information

I have a Ruby on Rails program with feature tests in Cucumber.
I just implemented a feature where an admin can create a new password for a client-user. Now, on the "edit client" page, there's an additional button that allows the admin to set the password. Now, I just need to make a cucumber test.
I am trying to base this off of the normal test for client changing password, and the test for admin changing the user's information. What I have is this:
Feature: Analyst changes client's password
As an Analyst
I want to change client's password
So that I can reset the client's account
Background:
Given the following client accounts
| email | password |
| user1#someorg.com | password |
And I am logged in as an admin
#javascript
Scenario: Update a Client user
Given I navigate to the Clients Management Page
When I edit the Client User "user1#someorg.com"
And I click on "button"
Then I should be on the Clients Password Page
#javascript
Scenario: Can change password if confirmation matches
Given I navigate to the Clients Password Page
And I enter "Password1" as the password
And I enter "Password1" as the password confirmation
And I submit the form
Then I should be taken to the Client Landing Page
And The client's password should be "Password1"
In the steps, I have:
Given /^I navigate to the Clients Password Page$/ do
client_management_index_page = ClientsPasswordPage.new Capybara.current_session
client_management_index_page.visit
end
Then /^I should be on the Clients Password Page$/ do
client_password_page = ClientsPasswordPage.new Capybara.current_session
expect(client_password_page).to be_current_page
end
and ClientsPaswordPage:
class ClientsPasswordPage
include PageMixin
include Rails.application.routes.url_helpers
def initialize session
initialize_page session, edit_admin_client_password_path
end
end
except that edit_admin_client_password_path takes an :id, for the user who's being edited. I can't figure out how to get that information into it.
In case it matters, I'm using Devise for the security stuff...
There are a few ways to do this. The simplest is to realize that you're only creating one client during the test so
Client.first # whatever class represents clients
will always be that client. Obviously that doesn't work if you have tests where you create one more than client, so then you can create instance variables in your cucumber steps which get set on the World and can then be accessed from other steps and passed to your page objects
When I edit the Client User "user1#someorg.com"
#current_client = Client.find_by(email: "user1#someorg.com") # obviously would actually be a parameter to the step
...
end
Then /^I should be on the Clients Password Page$/ do
client_password_page = ClientsPasswordPage.new Capybara.current_session, #current_client
expect(client_password_page).to be_current_page
end
of course without the page object overhead this would just become
Then /^I should be on the Clients Password Page$/ do
expect(page).to have_current_path(edit_admin_client_password_path(#current_client))
end
There are a number of things you can do to simplify this scenario. If you have simpler scenarios, with simpler step definitions then it will be easier to solve implementation problems like how you get a client in one step to be available in a second step.
The main way to simplify scenarios is to not have anything at all in the scenario that explains HOW you have implemented the functionality. If you take all the clicking on buttons, filling in fields, and visiting pages out of your scenarios you can focus on the business problem.
So how about
Background
Given there is a client
And I am logged in as an admin
Scenario: Change clients password
When I change the clients password
Then the client should have a new password
Note: This immediately raises the question 'How does the client find out about there new password?', which is what good simple scenarios do, they make you ask valuable questions. Answering this is probably out of scope here.
Now lets have a look at the implementation.
Given 'there is a client' do
#client = create_new_client
end
When 'I change the clients password' do
visit admin_change_password_path(#client)
change_client_password(client: #client)
end
Just this might be sufficient to get you on the right path. In addition something like
Given 'I am logged in as an admin' do
#i = create_admin_user
login_as(user: #i)
end
would help.
What we have done here is
Push the HOW down your stack so that now the code you right to make this work is out of your scenarios and step definitions
Used variable to communicate between steps the line #client = create_new_client creates a global (actually global to Cucumber::World) variable that is available in all step definitions
You can create helper methods by adding modules to Cucumber world and defining methods in them. Note these methods are global so you have to think carefully about names (there are very good reasons why these methods are global). So
module UserStepHelper
def create_new_client
...
end
def create_admin_user
...
end
def change_client_password(client: )
...
end
end
World UserStepHelper
Will create a helper method you can use in any of your step definitions.
You can see an example of this approach here. A project I used for a talk at CukeUp 2013. Perhaps you could use this as your tutorial example.

Devise user creation fails during Capybara integration testing (and Selenium webdriver)

I'm trying to create a user during an integration test to use for some operations. I'm using devise with :confirmable. The code is the following:
user = User.create({username: "user1", password: "pass1234", password_confirmation: "pass1234", email: "test#email.com"})
user.confirm!
fill_in "Username", :with => user.username
fill_in "Password", :with => user.password
click_button "Sign in"
The problem is that the login fails every time I try it. There are no errors about the user creation, but for some reason the user doesn't seem to "be there" when I try to login. I just get 'Invalid username or password' when I try to sign in. This seems like something to do with the fact that maybe Capybara/Selenium webdriver isn't waiting properly for the database operation to take place before it tries to sign in. If that's the case, how could I test it or fix it?
Is it "wrong" to even be trying to insert into the database during an integration test?
I don't use devise myself so can't really comment on the specifics of the problem you're encountering, but this question caught my eye:
Is it "wrong" to even be trying to insert into the database during an integration test?
Yes, I would say it generally is.
Your integration tests should test your code from the point of view of the user:
Expectations should only depend on what the user can actually see.
Actions should correspond only to what the user can actually do.
Inserting something into the database goes beyond the range of actions that the user has at their disposal. It is something for a unit test perhaps, but not for an integration test.
That being said, you could argue that seeding database data is a bit of an exception to this rule, since you're setting up context for your test (see my comments below).

Add extra documentation to rspec output

So, while I like Cucumber for its readability for integration testing and its ability to give us documentation we can share with the client easily, I also find it cumbersome from the development and testing speed standpoints.
I got to thinking today that if I could just print out messages to the RSpec documentation format that documented the "steps", then I could easily replicate the business features of Gherkin but in the simplicity of RSpec. But I can't figure out a way to do this.
What I want is to take something like this:
describe "Login and Authorization Tests" do
before (:each) do
docs "Given I have a user"
#user = FactoryGirl.create(:user)
end
it "A user can belong to one or more user roles" do
docs "And the user has no roles assigned"
#user.roles.length.should eq 0
docs "When I add two roles"
#user.roles << FactoryGirl.create(:role)
#user.roles << FactoryGirl.create(:role)
#user.reload
docs "Then the user should have two roles assigned"
#user.roles.length.should eq 2
end
end
and get this in the documentation
User
Login and Authorization Tests
A user can belong to one or more user roles
Given I have a user
And the user has no roles assigned"
When I add two roles
Then the user should have two roles assigned
Note that the message from "before" shows up in the docs too, and would show up with that line in every test below it.
I'm thinking of forking to see if I can add something like this in, but before I did that, does anyone know if something like this is possible already?
I also contacted the RSpec dev team and someone there posted this add-on called rspec-longrun that could be repurposed for this. I haven't had a chance to try it yet, but it looks very promising. And as a bonus, it includes timing information.
rspec-longrun: https://github.com/mdub/rspec-longrun
Thread on rspec-dev: https://github.com/rspec/rspec-dev/issues/34
You can try Steak, but the difference is not that big.
Or you can try Cucumber with RSpec matchers. In last case, you can fork RSpec and add a new formatter

Cookies within a cucumber test using capybara

As part of my integration tests for a website I am using cucumber with capybara. It seems that capybara cannot emulate the use of cookies.
For example I set the cookie when the user signs in:
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
However when I later fetch the value of cookies using cookies.inspect it returns {}
Is this a known limiting of capybara? How can I test a signed in user over multiple requests if this is the case?
I should add my test:
Scenario: User is signed in when they press Sign In
Given I have an existing account with email "bob#jones.com" and password "123456"
And I am on the signin page
When I fill in "Email" with "bob#jones.com"
And I fill in "Password" with "123456"
And I press "Sign In"
Then I should see "Welcome Bob Jones"
Here's a step that works for me. It sets a cookie "admin_cta_choice" to be equal to a model id derived from the input value.
Given /I have selected CTA "([^"]+)"/ do |cta_name|
cta = Cta.find_by_name(cta_name)
raise "no cta with name '#{cta_name}'" if cta.nil?
k = "admin_cta_choice"
v = cta.id
case Capybara.current_session.driver
when Capybara::Poltergeist::Driver
page.driver.set_cookie(k,v)
when Capybara::RackTest::Driver
headers = {}
Rack::Utils.set_cookie_header!(headers,k,v)
cookie_string = headers['Set-Cookie']
Capybara.current_session.driver.browser.set_cookie(cookie_string)
when Capybara::Selenium::Driver
page.driver.browser.manage.add_cookie(:name=>k, :value=>v)
else
raise "no cookie-setter implemented for driver #{Capybara.current_session.driver.class.name}"
end
end
here's a nice way to show the content of the cookies while running your feature
https://gist.github.com/484787
Why don't you just run the test with selenium? Just add #selenium tag before the scenario you want to run with a real browser. :)
That gist didn't work for me as of date of this posting, however this does, and is somewhat simpler:
http://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles/
Capybara doesn't have an API for reading and setting cookies.
However, you can simulate logging in with Capyabara very easily - just visit the login link. That will log you in, including setting all cookies for you.
To see this in action, just look at my example Rails app.

Ruby on Rails functional testing with the RESTful Authentication plugin

I started writing functional tests for my rails app today. I use the RESTful authentication plugin. I ran into a couple confusing things I hope someone can clarify for me.
1) I wrote a quick login function because most of the functions in my rails app require authentication.
def login_as(user)
#request.session[:user_id] = user ? user.id : nil
end
The issue I see with this function, is it basically fakes authentication. Should I be worried about this? Maybe it is okay to go this route as long as I test the true authentication method somewhere. Or maybe this is terrible practice.
2) The second confusing thing is that in some places in my functional tests, I need the full authentication process to happen. When a user is activated, I have the do_activate method create some initial objects for the user. It is analogous to the creation of a blank notebook object and pen object for a student application, if that makes sense.
So in order to properly test my application, I need the user to hit that activation state so those objects are created. I am currently using Factory Girl to create the user, and then calling the login_as function above to fake authentication.
I guess another option would be to skip the full authentication sequence and just create the blank objects with Factory Girl. I could test the proper authentication somewhere else.
What do you think? If I should go through the proper sequence, why isn't the code below invoking the do_activate function?
user = Factory.create(:user)
user.active = 1
user.save
Thank you!
Faking it is perfectly acceptable.
However, write other tests that ensure that the things you want protected are protected. So
test "it should show the profile page" do
user = Factory(:user)
login_as(user)
get :show, :id => user
assert_response :success
end
test "it should not show the profile page cos I'm not logged in" do
user = Factory(:user)
get :show, :id => user
assert_response :redirect
end
Feel free to hit me up for followups!

Resources