Quick summary: why can't capybara find the .admin-edit class?
So, I have built a site where there are published and unpublished articles and only the published articles are seen by guests while admins can see everything. Login is handled through devise and a simple erb expression determines if an article is shown or 'published'.
I list articles on the index action of my articles controller and render a partial to display the articles.
<% if article.published %>
<dl class="individual-article">
<dt><%= article.title %>
<% if current_user.try(:admin) %>
| <span class="admin-edit"><%= link_to 'Edit', edit_article_path(article) %></span>
<% end %><br>
<span class="article-tags">
<%= raw article.tags.map(&:name).map { |t| link_to t, tag_path(t) }.join(', ') %></span>
</dt>
<dd><%= truncate(article.body.html_safe, length: 200) %>
<%= link_to 'more', article_path(article) %>
</dd>
</dl>
<% end %>
This works as expected but I cannot test for it correctly. In particular, it returns false on expecting to find 'Edit' if the user is admin.
Here is my sign_in_spec:
require 'rails_helper'
RSpec.describe "SignIns", type: :request do
describe "the sign in path" do
let(:user) { FactoryGirl.create(:user) }
let(:admin) { FactoryGirl.create(:admin) }
let(:article) { FactoryGirl.create(:article) }
let(:published) { FactoryGirl.create(:published) }
it "lets a valid user login and redirects to main page" do
visit '/users/sign_in'
fill_in 'user_email', :with => admin.email
fill_in 'user_password', :with => admin.password
click_button 'Log in'
expect(current_path).to eq '/'
expect(page).to have_css('span.admin-edit')
end
end
And here is my article factory:
FactoryGirl.define do
factory :article do
title 'Title'
body 'Content'
factory :published do
published true
end
end
And here is my user factory:
FactoryGirl.define do
factory :user do
email 'user#gmail.com'
password 'password'
factory :admin do
admin true
end
end
end
Here is the error:
1) SignIns the sign in path lets a valid user login and redirects to main page
Failure/Error: expect(page).to have_css('span.admin-edit')
expected #has_css?("span.admin-edit") to return true, got false
# ./spec/requests/sign_ins_spec.rb:18:in `block (3 levels) in <top (required)>'
I have tried the following:
Eliminating the extra article if rspec had a problem with multiple classes
Changing have_css to have_selector and selecting the anchor tag
Drawing out the entire DOM root from html body ...
Checking if it was working outside of the spec by manually logging in as user with admin privs -> it does.
Tried deleting unpublished vs published article distinction but still fails.
Tried removing erb condition to check if article is published in view but still fails.
Tried making sure it wasn't loading via ajax (has backup in will_paginate) but fails.
What am I doing wrong?
Edit
It now works if I avoid using the FactoryGirl importing:
#article = Article.create(title: 'Title', body: 'body', published: true)
Instead of
let(:published) { FactoryGirl.create(:published) }
No idea why.
RSpec lazily assigns let variables, so at the time you display your page, neither the published nor unpublished articles exist. You need to use let! or before or otherwise ensure the objects get created prior to display of your page.
Related
During my RSpec journey I want to test 3 methods from my user model, each method is dependent on each other. They are some kind of helpers method for gem stock_qoute and just checking whether the search item (stock) has been already added to the portfolio or whether the user has already reached the limit of added items (10 stocks). Those methods are used in view.
I was trying to mock this gem but I think this is not the simplest way to do so. I think I can skip the whole gem and use only variable but I have no idea how to do so. I'm using devise and Rspec without FactoryBot.
user.rb
has_many :user_stocks
has_many :stocks, through: :user_stocks
def stock_already_added?(ticker_symbol)
stock = Stock.find_by_ticker(ticker_symbol)
return false unless stock
user_stocks.where(stock_id: stock.id).exists?
end
def under_stock_limit?
(user_stocks.count < 10)
end
def can_add_stock?(ticker_symbol)
under_stock_limit? && !stock_already_added?(ticker_symbol)
end
find_by_ticker method from different model:
def self.find_by_ticker(ticker_symbol)
where(ticker: ticker_symbol).first
end
view file:
<% if current_user.can_add_stock?(#stock.ticker) %>
<%= link_to 'Add stocks', user_stocks_path(user: current_user, stock_ticker: #stock.ticker),
class: 'btn btn-xs btn-success', method: :post %>
<% else %>
<span class="label label-default">
Stock cannot be added because you have added
<% if !current_user.under_stock_limit? %>
10 stocks
<% end %>
<% if current_user.stock_already_added?(#stock.ticker) %>
this stock
<%= link_to 'Remove', user_stock_path, method: :delete %>
<% end %>
</span>
<% end %>
user_spec.rb:
describe 'Add stock' do
user = User.create(email: 'test#example.com', password: 'password')
context 'when user add the same stock' do
let(:stock) { Stock.new(name: 'Goldman Sachs', ticker: 'GS', last_price: 112.4) }
it "return false" do
user_stocks = UserStock.new(user: user, stock: stock)
expect(user_stocks.can_add_stock?('GS')).to eq false
end
end
end
Right now I've got an error:
1) User Add stock when user add the same stock return false
Failure/Error: expect(user_stocks.can_add_stock?('GS')).to eq true
NoMethodError:
undefined method `can_add_stock?' for #<UserStock:0x00007fb0efa4d3a8>
# ./spec/models/user_spec.rb:29:in `block (4 levels) in <top (required)>'
Why do you call can_add_stock? on user_stock, when it is method of the User model? You need to check stocks for user
describe 'Add stock' do
let(:user) { User.create(email: 'test#example.com', password: 'password') }
context 'when user add the same stock' do
let(:stock) { Stock.create(name: 'Goldman Sachs', ticker: 'GS', last_price: 112.4) }
it "return false" do
user.stocks << stock
expect(user.can_add_stock?('GS')).to eq false
end
end
end
I'm trying to run a test on will_paginate. I know that it technically works, but I can't get the spec to work because of my inability to create multiple records. I'm using Capybara and Rspec on with Ruby on Rails.
Here is what I have in my feature spec.
RSpec.describe "Users Index", type: :feature do
describe "Pagination" do
let(:valid_user) { create(:user, name: "Mogli") }
let(:other_user) { create(:user, 50) }
it "successfully paginates" do
log_in_as_feature(valid_user)
visit users_path
puts URI.parse(current_url)
expect(page).to have_css('div.pagination')
expect(page).to have_link(href: user_path(valid_user), text: valid_user.name)
first_page_of_users = User.paginate(page: 1)
first_page_of_users.each do |user|
expect(page).to have_link(href: user_path(user), text: user.name)
unless user == valid_user
expect(page).to have_link(href: user_path(user), text: "delete")
end
end
end
end
end
My factory is simple:
FactoryGirl.define do
sequence(:name) { |n| "Person#{n}" }
factory :user do
name
email { "#{name}#example.com" }
password "foobar"
password_confirmation "foobar"
activated true
activated_at Time.zone.now
end
end
My fourth line is the culprit. It's not actually building the users. I tried to use a FactoryGirl.create(:user, 50), but that ends up breaking 27 tests across the board, and I have to reset the test database.
I don't know how else to create more than one dummy user at once, all the while keeping Mogli as first. Any insight is appreciated.
Edit: If I commented the have_css test, then my tests pass.
Here is the error of the div
1) Users Index Pagination successfully paginates
Failure/Error: expect(page).to have_css('div.pagination')
expected to find css "div.pagination" but there were no matches
# ./spec/features/users_index_spec.rb:13:inblock (3 levels) in '
Finished in 0.82368 seconds (files took 2.17 seconds to load)`
EDIT: adding my partial and index.html.erb view.
My view just renders #users partial
which is:
1 <li>
2 <%= gravatar_for user, size: 50 %>
3 <%= link_to user.name, user %>
4 <% if current_user.admin? && !current_user?(user) %>
5 | <%= link_to "delete", user, method: :delete,
6 data: { confirm: "You sure?" } %>
7 <% end %>
8
9 </li>
Your example has a few issues. As mentioned by others let is lazily evaluated, so to create objects that aren't directly referenced you would need to use let!. Additionally, FactoryGirl's create doesn't take a number of records to produce, you need create_list for that. Finally, Capybara's have_link takes the text of the link you're looking for as the first parameter so there's no need to pass a :text option
RSpec.describe "Users Index", type: :feature do
describe "Pagination" do
let!(:valid_user) { create(:user, name: "Mogli") }
let!(:other_users) { create_list(:user, 50) }
it "successfully paginates" do
log_in_as_feature(valid_user)
visit users_path
puts URI.parse(current_url)
expect(page).to have_css('div.pagination')
expect(page).to have_link(valid_user.name, href: user_path(valid_user))
User.paginate(page: 1).each do |user|
expect(page).to have_link(user.name, href: user_path(user))
expect(page).to have_link("delete", href: user_path(user)) unless user == valid_user
end
end
end
First of all, let is lazy-evaluated. That means it will not be evaluated until the moment you call it in your spec. You don't use other_user in your spec, so it is not evaluated.
Secondly, if you want to create a list of 50 users to set up your spec, use a before block:
before do
# using create_list
create_list(:user, 50)
# OR just
50.times { create(:user) }
end
it "successfully paginates" do
# ...
end
As mentioned already by Jan, let is lazily-evaluated. If you don't use other_user, it will never be created. There's an eager-counterpart, though.
let!(:other_user) { create(:user, 50) }
This one is always created.
When I run my 'webinar' specs alone they seem to always past, but if I try the whole suite it only passes one of the tests about 50% or the time. I tested this using the same seed each time to see if it had anything to do with the order in which the tests are being executed.
If I slow down my test by putting a sleep in the middle of it then it magically starts passing 100% again. Obviously I don't want to rely on a weak work-around like this and want to figure how to actually fix my problem.
require "spec_helper"
require "spec_helper"
describe "ProgramManager::Webinars" do
let(:program) { create(:program) }
let(:superuser) { create(:superuser) }
describe "#index" do
before { login_as(superuser) }
let(:delete) { 'Delete' }
it "displays an edit and destroy link for all webinars" do
w1, w2, w3 = create(:webinar, program: program), create(:webinar, program: program), create(:webinar, program: program)
visit program_webinars_path(program)
[w1, w2, w3].each do |webinar|
expect(page).to have_link webinar.name, href: edit_program_webinar_path(program, webinar)
expect(page).to have_link '', href: destroy_warnings_program_webinar_path(program, webinar)
end
end
it "has a link to create a new webinar" do
visit program_webinars_path(program)
expect(page).to have_content 'New Webinar'
end
it "deletes a webinar", js: true do #THIS IS THE TEST THAT SOMETIMES FAILS
webinar = create(:webinar, program: program)
visit program_webinars_path(program)
all('.destroy').last.click
wait_for_ajax
sleep(1.second) #THIS IS THE SLEEP THAT FIXES THE FAILURE
expect { click_link delete }.to change(Webinar, :count).by(-1)
end
end
.
FactoryGirl.define do
factory :webinar do
program
name "some name"
url "some url"
description "some description"
speaker "some speaker"
starts_at Time.now
end
end
.
FactoryGirl.define do
factory :program do
contract
program_manager factory: :user
sequence(:name) { |n| "Program-#{n}" }
description { "Program description" }
starts_at { Time.now }
ends_at { Time.now + 10.days }
color { "#33f" }
program_type { "some program type" }
verified { false }
end
end
.
<div class="col-md-4">
<%= link_to "<span class='glyphicon glyphicon-plus'></span>".html_safe, new_program_webinar_path(#program), class: 'new-webinar', data: { toggle: 'tooltip', title: 'Add a Webinar' } %>
<h4>Current Webinars</h4>
<% if #webinars.empty? %>
<p>There are currently no webinars to display.</p>
<% else %>
<table class="table table-condensed">
<% #webinars.each do |webinar| %>
<tr>
<%= content_tag :td, class: pm_setup_classes(webinar, #webinar) do %>
<%= link_to "<span class='glyphicon glyphicon-remove'></span>".html_safe, destroy_warnings_program_webinar_path(#program, webinar), class: 'destroy', data: { toggle: 'modal', target: '#ajax-modal' } %>
<%= link_to webinar.name, edit_program_webinar_path(#program, webinar), class: 'webinar' %>
<% end %>
</tr>
<% end %>
</table>
<% end %>
<%= link_to 'New Webinar', new_program_webinar_path(#program), class: 'btn btn-success btn-block' %>
</div>
.
class ProgramManager::WebinarsController < ProgramManager::ApplicationController
before_filter :authenticate_user!
before_filter :webinars
def new
#webinar = #webinars.build
clean_webinars
end
def create
#webinar = #program.webinars.build(params[:webinar])
clean_webinars
if #webinar.save
redirect_to program_webinars_path(#program), success: "Webinar successfully created"
else
render :new
end
end
def edit
#webinar = #program.webinars.find(params[:id])
end
def update
#webinar = #program.webinars.find(params[:id])
if #webinar.update(params[:webinar])
redirect_to program_webinars_path(#program), success: "Webinar successfully updated"
else
render :edit
end
end
def destroy
#webinar = #program.webinars.find(params[:id])
if #webinar.destroy
redirect_to program_webinars_path(#program), success: "Webinar removed successfully"
else
render :index
end
end
def destroy_warnings
#webinar = #program.webinars.find(params[:id])
render layout: false
end
private
def clean_webinars
#webinars = #webinars.delete_if(&:new_record?)
end
def webinars
#webinars = #program.webinars
end
end
I am sorry there is so much code associated with this question. I'm just trying to provide as much info as I can since I have no idea where this bug is from or how to fix it
The problem seemed to ultimately be a javascript fade in. The delete button we are trying to press is on a modal that fades in to alert you of the repercussions of your deletion and asks you to confirm. Our wait_for_ajax() helper waited until all active jQuery connections were resolved. The connections would finish so it would move on to the next line of code which told it to click a link on the delete link. The html had a delete link in it so Capybara can find it, but since it is actively fading in... the click doesn't work and the test fails!
You can adjust the Capybara.default_wait_time = 5 (default is 2s).
See doc in "Asynchronous JavaScript (Ajax and friends)" section of https://github.com/jnicklas/capybara
This will change the total amount of time Capybara waits before giving up on finding a node, but should not affect the interval at which it will keep trying to check.
It might also be asynchronous operation in your database. We were getting random failures in our specs until we set fsync = on in our PostgreSQL configuration. I think that is a better option than stuffing sleep(#) everywhere.
I don't see the error trace, but if not quite all data is ready on a page you can use the specific gem called rspec-wait to wait a condition for a time (by default 3 sec). So, for example the rspec code become the following:
visit program_webinars_path(program)
wait_for(page).to have_content 'New Webinar'
This allow you to wait for some specific HTML (if required) for a time.
I've been following the Rails Tutorial by Michael Hartl. Actually I already finished it, but I am having some problems with some refactoring I did for the last exercise of the final chapter. All I did was changing the show view for the user model (show.html.erb) from this:
<section>
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
</section>
To this:
<section>
<%= render 'shared/user_info' %>
</section>
And in the partial(_user_info.html.erb) I have this:
<a href="<%= user_path(current_user) %>">
<%= gravatar_for current_user, size: 52 %>
</a>
<h1> <%= current_user.name %> </h1>
<% unless current_page?(user_path(current_user)) %>
<span> <%= link_to "view my profile", current_user %> </span>
<span> <%= pluralize(current_user.microposts.count, "micropost") %> </span>
<% end %>
Everything works fine on the browser, but for some reason rspec is failing some tests, I suspect rspec is having problems calling the current_user method which is defined in sessions_helper.rb, which by the way is included in application_helper.rb. The gravatar_for function is defined in users_helper.rb. Here is the error I get from the tests:
Failure/Error: before { visit user_path(user) }
ActionView::Template::Error:
undefined method email' for nil:NilClass
# ./app/helpers/users_helper.rb:4:ingravatar_for'
# ./app/views/shared/_user_info.html.erb:1:in _app_views_shared__user_info_html_erb___3480157814439046731_47327400'
# ./app/views/users/show.html.erb:5:in_app_views_users_show_html_erb___1252254778347368838_47378900'
# ./spec/requests/user_pages_spec.rb:58:in `block (3 levels) in '
I would appreciate if you could help me identify what is going on here. I could find different ways to do the same thing but I am just very curious about this. I am not very experienced in Rails which is why I followed the tutorial so forgive me if I am missing something obvious. Thanks.
I think I got the solution. Although I am still confused. Here is the code for the tests as they were before the refactoring mentioned in my question (as they are in the tutorial, they all were passing):
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }
before { visit user_path(user) }
it { should have_selector('h1', text: user.name) }
it { should have_selector('title', text: user.name) }
describe "microposts" do
it { should have_content(m1.content) }
it { should have_content(m2.content) }
it { should have_content(user.microposts.count) }
end #some other tests for show page continue, also having the same behaviour
end
While reading the tests to see if there was a mistake I started wondering why those tests were passing if I was not signing the user in, so I added the code to sign in the user in the before block, like this:
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }
before do
valid_signin user
visit user_path(user)
end
it { should have_selector('h1', text: user.name) }
it { should have_selector('title', text: user.name) }
describe "microposts" do
it { should have_content(m1.content) }
it { should have_content(m2.content) }
it { should have_content(user.microposts.count) }
end #some other tests for show page continue, also having the same behaviour
end
And now all tests pass. I know is seems silly and obvious to sign in the user, however that's how the tests are in the tutorial and they were working before. Here is the updated test file if you want to check it. All changes are now committed in my github repo. Thank you all for your help.
Poking around your repo, this may be the problem: there was a minor bug reported on June 25 in the Rails Tutorial with regards to the setting of current_user in the sign_in and sign_out methods in the app/helpers/sessions_helper.rb file. The notice doesn't seem to have been posted on the Rails Tutorial News site, but it was sent to subscribers and it is reflected in the current online book saying to change:
self.current_user = user # in the sign_in method
self.current_user = nil # in the sign_out method
So, try updating your sessions_helper.rb, and see if that stops you from getting a nil user.
Sounds like you don't have any users in your test database. The development database and test database are two different things. Rspec uses the test database when it's running tests.
To setup the test database you do rake db:test:prepare.
Then you need to populate the test database when you run your specs. One great way to do that is with the Factory Girl gem.
I have a very simple RSpec Capybara have_selector() that doesn't seem to be working and I can't figure out why... but probably something simple.
Here's the RSpec request test:
describe 'with valid information'
let(:user) { FactoryGirl.create(:user) }
before do
fill_in 'Email', with: user.email
fill_in 'Password, with: user.password
click_button 'Login'
end
it { should have_selector('p', text: user.first_name) }
...
end
In the new.html.erb for the session, I just have something like this:
<% if logged_in? %>
<p><%= current_user.first_name %></p>
<% else %>
<%= form_for(:session, url: sessions_path) do |f| %>
...
<% end %>
The login form session#new works perfectly in the browser. I can login just fine and am able to view the first name in a p tag. The problem seems to be the capybara part, because the other it test checking for the h1 tag on the page (which is present regardless of being logged in) works fine.
Does anyone know what the problem could be?
Thanks in advance for any help!!
More than likely, your login is failing for one reason or another.
Change your test to look like this:
it "should show me my first name" do
save_and_open_page
should have_selector('p', text: user.first_name)
end
That will open your browser and show you the page as the test browser currently sees it. (You may need to add the launchy gem to your Gemfile.)
There are a few other diagnostics that would help, for instance, adding a test or a puts that checks what page you landed on, such as:
it "redirects to the home page after log in" do
current_path.should == root_path
end
fill_in 'Password, with: user.password
not like that. You should use this line,
fill_in 'Password', with: user.password