Dynamic expectations in Rspec - ruby-on-rails

I am trying to test a dynamic url path through an Rspec expectation, as below:
describe 'registering a user' do
context 'with valid data' do
it 'confirms user registration' do
visit '/users/new'
fill_in 'Name here...', with: 'johnny bloggs'
fill_in 'Your email here...', with: 'test1#test.com'
click_button 'Notify me'
expect(current_path).to eq '/users/#user.id'
expect(page).to have_content "johhny"
expect(page).not_to have_content "Sign up"
end
end
end
This is a feature test not a model unit test so I suppose this sort of object attribute , #user.id, expectation shouldn't be in a feature test. But testing the redirection to a certain path i guess is a valid thing for a feature test to be checking, and a key part of the functionality that i'm testing is that the action redirects to an object specific show page?!
So a) how should i correctly be testing this redirect thats new path involves a dynamic attribute and b) whether or not the correct thing to be doing here is testing a 'dynamic attribute' how would it be possible to test dynamic content within an rspec test? .erb perhaps?
Thank you in advance for the enlightenment.

You can use plain string interpolation:
expect(current_path).to eq "/users/#{ User.last.id }"
Or a route helper:
expect(current_path).to eq user_path(User.last)

Related

Why do these tests fail when run simultaneously, yet each passes individually?

I am experiencing strange very test behavior, with logged in state being handled inconsistently.
The spec logs a user in, visits a (nested or un-nested) index page, and checks that the correct content is displayed. Records are fetched asynchronously, though I don't think this should have an impact.
When each spec is run individually, they each pass. When all specs are run together, they fail because the expected content is missing. Using save_and_open_page reveals this is because the login page is being rendered, rather than the expected index page.
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
The tests look something like this
let(:user) {create :user}
let(:team) {create :team}
let(:country) {create :country}
before :each do
login_as( user, scope: :user )
end
describe 'unnested' do
it 'should have the expected content', :js do
visit users_path
is_expected.to have_content "some content on the page"
end
end
describe 'nested by team' do
it 'should have the expected content', :js do
visit team_users_path(team)
is_expected.to have_content "some content on the page"
end
end
describe 'nested by nationality' do
it 'should have the expected content', :js do
visit country_users_path(country)
is_expected.to have_content "some content on the page"
end
end
The specs all require javascript (I don't know whether that is important here).
Authentication is handled by Devise, and my rails_helper.rb includes
config.append_after(:each) do
DatabaseCleaner.clean
Warden.test_reset!
end
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
It took a long time to get to the bottom of this. Posting this hear in case it is of help to anyone else encountering the same issue.
After much searching I eventually found this small mention that login_as may not work with Poltergeist when js is enabled on your test scenarios.
I tried the suggested fix to deal with shared DB connections. Unfortunately this resulted in the following errors:
PG::DuplicatePstatement at /session/users/signin
ERROR: prepared statement "a1" already exists
I tried using the Transactional Capybara gem, but this did not seem to work well with Poltergeist.
Eventually I abandonned login_as completely, and instead wrote a short method that visits the login page, fills in email and password, and logs in that way.
This solution appears to be working. It adds a little overhead, so I'm only using it for tests with JS.
If you are using Capybara gem then there is no need to use :js with test cases
What I did if this helps-
scenario "visit with user signed in" do
user = FactoryGirl.create(:user)
login_as(user, :scope => :user)
visit "/"
expect(current_path).to eq('/')
expect(page).to have_title "Some Random Title"
end
The other way you can login user using feature specs like-
feature 'User signs in' do
before :each do
#user = FactoryGirl.create(:user)
end
scenario "Signing in with correct credentials" do
visit "/"
fill_in "Email", with: #user.email
fill_in "Password", with: #user.password
click_button "Log In"
expect(current_path).to eq("/login/useremail/verification")
expect(page).to have_content "Signed in successfully"
end
end
If your pages are ajax then refer to this https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara

Capybara Rspec Rails get ID for path

Using capybara/rspec to test rails. Want to check current path is generated correctly with the id but cant access the created Contact id.
Example expectation:
localhost:3000/contacts/27
Example recieved:
localhost:3000/contacts/
Code base:
feature 'contacts' do
before do
visit '/'
click_link 'Sign up'
fill_in 'Email', with: 'test#test.com'
fill_in 'Password', with: '123456'
fill_in 'Password confirmation', with: '123456'
click_button 'Sign up'
click_link 'Add a contact'
fill_in 'Firstname', with: 'John'
fill_in 'Surname', with: 'Jones'
fill_in 'Email', with: 'test#test.com'
fill_in 'Phone', with: '223344'
attach_file('contact[image]', Rails.root + 'spec/mouse1.jpeg')
click_button 'Create Contact'
end
context 'view a contact' do
scenario 'click contact to view details' do
click_link('Mouse1')
expect(page).to have_content 'John Jones 223344 test#test.com'
expect(page).to have_xpath("//img[contains(#src, \/html/body/a[2]/img\)]")
expect(page).to have_current_path(contact_path("#{#contact.id}"))
end
end
Surprised the interpolation hasn't worked and throws error undefined method 'id' for NilClass using the below. Clearly it cant access the id.
expect(page).to have_current_path(contact_path("#{#contact.id}"))
Also tried swapping it out with #p = Contact.find_by_id(params[:id]) then passing in the #p in the interpolation. But throws error undefined local variable or method params
Any ideas/thoughts?
You can't access your controllers instance variables from within a feature test. You can however access the database, and since you've only created one contact in this test first or last should work -
expect(page).to have_current_path(contact_path("#{Contact.last.id}"))
That being said, signing up a user and creating the contact through the UI when your test is only checking that an existing contact can be viewed doesn't make a lot of sense when you could just create the database records for your feature tests. You probably want to look into something along the line of FactoryGirl for building your feature test objects.

Avoid code duplication in Rspec + Capybara

Let's say I have test on new input and test on new input when input is invalid (first test is on case input is valid).
For example (from my code):
scenario "valid input saving" do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
within("#student_0") do
fill_in "Имя", :with => "Name"
fill_in "Фамилия", :with => "Surname"
fill_in "Электронная почта", :with => "randommail#mail.com"
end
print page.html
click_button "Save"
expect(page).to have_current_path program_stream_path(#program, #stream)
#...other code
end
Obviously, test that checks behavior on invalid input repeats this part:
scenario "invalid input leads to correct input page" do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
#other code
How to avoid this copy-paste way?
You can use before blocks for this kind of thing
feature "..." do
before :each do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
end
scenario "valid input saving"
#unique code for this scenario
end
scenario "invalid input leads to correct input page"
# unique code for this scenario
end
end
The outer feature block could be a describe or a scenario block if wanted/needed since you can nest multiple levels. If you need to use the code across multiple feature files then it makes sense to move it to a method in one of your spec helper files.
You can place it in a method in your spec file like this and reuse it.
scenario "valid input saving" do
your_named_method
...
end
scenario "invalid input leads to correct input page" do
your_named_method
...
end
def your_named_method
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
end
A great way to avoid duplication when testing with Capybara is to use Capybara Test Helpers.
RSpec.feature 'Program Stream', test_helpers: [:programs] do
before { visit program_stream_path(#program, #stream) }
scenario 'valid input saving' do
programs.click_to_add_student
programs.should.be_adding_a_student(#stream)
programs.add_student(name: 'Имя', surname: 'Фамилия', email: 'randommail#mail.com')
programs.should.have_new_student('Имя')
end
scenario 'invalid input leads to correct input page' do
programs.click_to_add_student
programs.should.be_adding_a_student(#stream)
programs.add_student(name: nil, surname: nil, email: nil)
programs.should.have_invalid_form("Name can't be blank")
end
end
Besides reducing code duplication, it has the benefit of being a lot more descriptive, which can help to make tests a lot easier to maintain in the long term.
class ProgramsTestHelper < Capybara::TestHelper
# Actions: Encapsulate complex actions to provide a cleaner interface.
def click_to_add_student
click_link '#link'
fill_in '#fill_in', with: '1')
click_button "Next"
end
def add_student(name:, surname:, email:)
fill_in 'Имя', with: name
fill_in 'Фамилия', with: surname
fill_in 'Электронная почта', with: email
click_button 'Save'
end
# Assertions: Allow to check on element properties while keeping it DRY.
def be_adding_a_student(stream)
have_current_path urls.new_students_list_stream_path(stream)
end
def have_new_student(name)
have_content(name)
end
def have_invalid_form(message)
have('form', text: message)
end
end
Have in mind that you could choose to combine click_to_add_student with add_student, running assertions inside the helper methods. It all boils down to how much granularity you need in tests.
Passing blocks to methods is also a nice way to customize interactions or outcomes.

Determining Form Submission with RSpec/Capybara

I have a project in which I am using RSpec and Capybara to do unit testing. I have fully flushed out model and controller tests which are passing nicely and handle the heavy lifting for pre-database validation.
I am now testing user experience and front end items and want to know how i could verify that a form did NOT submit. If a user mismatches passwords or some other erred data, I have script to set the error and prevent submission.
I know i could search for error text but where there a way to check that the 'submit' never happened and feel confident that no server trip was made.
I want something like:
it "should not sumbit if user name is less than 3 characters" do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
# HOW DO I DO THIS?
expect( ... ).not_to submit_to_server
end
This is not something that you should be testing in an integration test. In integration tests you are taking the point of view of the end user, and therefore you should only be testing what the user can actually see. If the only evidence the user sees that the form has not been submitted is an error message, then that is what you should test for.
In integration tests we most test what the user would see like if an empty field is given then there must be error message according to validations. But still if you want you check as follow
describe "User Registration" do
before do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
end
it "should not sumbit if user name is less than 3 characters" do
page.should have_content "your error message"
end
it "should create the user and redirect to blah_path" do
current_path.should eq blah_path
end
it "should add user in users table" do
expect { user.create }.to change(User, :count).from(0).to(1)
end
end

Capybara: fill_in action locator

So I have this spec
describe "visit /signup" do
before(:each) do
get signup_path
end
it "has an email input field" do
page.has_field?("#user_email")
end
it "accepts an email address" do
page.fill_in('#user_email', :with=>Faker::Internet.email)
end
end
The first test (has an email input) passes, the second fails with
Failure/Error: page.fill_in('#user_email', :with=>Faker::Internet.email)
Capybara::ElementNotFound:
cannot fill in, no text field, text area or password field with id, name, or label '#user_email' found
The input[type='text'] element exists on the page with that DOM ID, have tried locating with the ID with and without the hash, and using its input:name as a locator too.
What am I doing wrong?
It's because you're using get when you should be using visit inside the before block. This:
before(:each) do
get signup_path
end
Should be this:
before(:each) do
visit signup_path
end
Otherwise you're telling Rack::Test to visit that path, not Capybara! A small distinction that trips quite a few people up frequently!
maybe you should remove the #, e.g.
fill_in('user_email', :with=>Faker::Internet.email)
I think fill_in is not on page. Just use fill_in:
describe "visit /signup" do
before(:each) do
get signup_path
end
it "has an email input field" do
page.has_field?("#user_email")
end
it "accepts an email address" do
fill_in('#user_email', :with=>Faker::Internet.email)
end
end

Resources