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.
Related
I have difficulty understanding the Rspec logic of creating data to use in tests.
I have a couple of scenarios and all result in errors except for the first scenario. Error meaning that when i print the page, the record is not rendered in the HTML, meaning that the variable is not created.
Here are my factories:
article:
FactoryGirl.define do
factory :article do
title "First test article"
summary "Summary of first article"
description "This is the first test article."
user
end
end
comment:
FactoryGirl.define do
factory :comment do
sequence(:content) { |n| "comment text #{n}" }
article
user
end
end
spec/features/article_spec.rb
1) Explicitly create the comment variable within the rspec test.
require 'rails_helper'
describe "Comments on article" do
let!(:user) { FactoryGirl.create(:user) }
let!(:article) { FactoryGirl.create(:article) }
let!(:comment) {Comment.create(content:"Some comments", article_id: article.id, user_id: user.id)}
before do
login_as(user, :scope => :user)
visit article_path(article)
end
describe 'edit', js: true do
let!(:comment) {Comment.create(content:"Some comments", article_id: article.id, user_id: user.id)}
it 'a comment can be edited through ajax' do
print page.html
find("a[href = '/articles/#{article.friendly_id}/comments/#{comment.id}/edit']").click
expect(page).to have_css('#comment-content', text: "Some comments")
within('.card-block') do
fill_in 'comment[content]', with: "Edited comments"
click_on "Save"
end
expect(page).to have_css('#comment-content', text: "Edited comments")
end
end
end
2) Replacing let!(:comment) {Comment.create(content:"Some comments", article_id: article.id, user_id: user.id)} with below:
let!(:comment) { FactoryGirl.create(:comment) }
3) Place the let! statement before the first 'it' block
describe 'edit', js: true do
let!(:comment) {Comment.create(content:"Some comments", article_id: article.id, user_id: user.id)}
it 'a comment can be edited through ajax' do
print page.html
find("a[href = '/articles/#{article.friendly_id}/comments/#{comment.id}/edit']").click
expect(page).to have_css('#comment-content', text: "Some comments")
within('.card-block') do
fill_in 'comment[content]', with: "Edited comments"
click_on "Save"
end
expect(page).to have_css('#comment-content', text: "Edited comments")
end
end
UPDATE:
I find setting up test data/variables to be a big pain/stumbling block for me as a Rspec newbie. Here are some references i found that helped me:
1) When is using instance variables more advantageous than using let()?
2) https://www.ombulabs.com/blog/rails/rspec/ruby/let-vs-instance.html
let!(:comment) {Comment.create(content:"Some comments", article_id: article.id, user_id: user.id)}
let!(:comment) { FactoryGirl.create(:comment) }
These are not going to create the same comments - in the second case, the comment that is created will not be associated with user or article (but a new user and a new article).
Are you inspecting the variable and it is nil, or are you just going by what you see on the page? If you're going by what's on the page, is it possible it is only showing you comments associated with the given article/user?
As for the other issue, the before block is executed before the comment variable created inside the describe - so the comment does not exist until after you've navigated to the page.
I'm writing some views tests in an application and my tests expects something like:
describe 'form' do
it 'has a search form' do
render
expect(rendered).to have_selector 'form[id=mock_search]'
end
it 'has a name filter' do
render
expect(rendered).to have_selector 'label[for=q_name_cont]', text: 'Nome do simulado'
expect(rendered).to have_selector 'input[id=q_name_cont]'
end
it 'has a submit button' do
render
expect(rendered).to have_selector 'input[type=submit][value="Buscar"][name=commit]'
end
it 'has a reset button' do
render
expect(rendered).to have_selector 'input[type=submit][value="Limpar filtros"]'
end
end
But I have and before(:each) that iterates too many requests on application making my tests spend 25 seconds to run. I've changed to before(:all) and then become to 4 seconds.
Should I still use before(:each)?
Why before(:all) were not recommended?
EDIT: My before iterations:
before(:each) do
#school = build(:school)
#teacher = build(:teacher)
build_list(:mock_with_proccessed_statistics, 2, school: #school, teacher: #teacher)
#mocks = Mock.page(nil)
#q = Mock.ransack
allow(view).to receive(:current_school).and_return(#school)
allow(view).to receive(:format_date) { |date, format| date.strftime(format) }
end
describe 'form' do
#
#let! will create create instance variable school
#inside your test case and assign your school build. Different between
#let! and let is: let! create before every task. You won't need it to put
#on before block and let doesn't do that you have to call it explicitly to
#create that mock and get values..
let!(:school) { build(:school) }
let!(:teacher) { build(:teacher) }
let!(:statistics) { build_list(:mock_with_proccessed_statistics, 2, school: #school, teacher: #teacher) }
let!(:mocks) { Mock.page(nil) }
subject { render }
it 'has a search form' do
expect{ subject }.to have_selector 'form[id=mock_search]'
end
it 'has a name filter' do
expect{ subject }.to have_selector 'label[for=q_name_cont]', text: 'Nome do simulado'
expect{ subject }.to have_selector 'input[id=q_name_cont]'
end
end
before(:each) execute before each test case executes. and before(:all) once before all test cases inside a current context.
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.
I' on Micheal Hartl tutorial chapter 9 .And i'm trying to check for if the users name are displayed on the page . but i'm getting this error
Failure/Error: User.paginate.(page:1).each do |user|
ArgumentError:
wrong number of arguments (0 for 1)
i probably got to change this part User.paginate.(page: 1).each do |user|. but what do i have to do ?.
here the code inside the rspect directory.
describe "index" do
let(:user) { FactoryGirl.create(:user) }
before do
sign_in user
30.times { FactoryGirl.create(:user) }
visit users_path
end
it { should have_selector('title', text: 'All users') }
it { should have_selector('h1', text: 'All users') }
it "should list each user" do
User.paginate.(page: 1).each do |user|
page.should have_selector('li>a', text: user.name )
end
end
end
you have an extra period after paginate. It should be:
User.paginate(page: 1).each
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.