Rails 4.2, rspec 3.4.0
When i create 'question' by hands, flash notice is in the place. But when i go with rspec, flash hash is empty and notice isn't exist. I tried to run rails server in test environment to ensure all works when doing manually. It is. My recent project's tests work well with same gemset. So i developed this is rspec fault. And now i don't know where to go.
question_controller.rb
def create
#question = Question.new(question_params)
if #question.save
flash[:notice] = t('question.created')
redirect_to #question
else
flash[:alert] = t('question.not_created')
render 'new'
end
end
features/creating_questions_spec.rb
require 'rails_helper'
feature 'Creating Questions' do
before do
visit '/'
click_link 'English'
end
scenario "can create question" do
fill_in 'question_body', with: 'What is this?'
click_button t('question.create')
expect(page).to have_content(t 'question.created')
end
rspec output
Failure/Error: expect(page).to have_content(t 'question.created')
expected to find text "Question has been created." in "Home Sign in Sign
up Русский English What is this?
layout
<% flash.each do |key, value| %>
<div class='flash' id='<%= key %>'>
<%= value %>
</div> <% end %>
<%= yield %>
Ok, after long research i have found the answer.
To implement i18n in my project i used 138 railscast by Ryan Bates. From there i took this function:
application_controller.rb
def default_url_options(options={})
{:host => "localhost:3000",
:locale => I18n.locale}
end
String :host => "localhost:3000" causes the trouble . Comment it and viola! - all works fine again
Related
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.
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.
After a factory girl create I have the following
it "should display the email of user if public" do
#user.update_attributes(:public_email => true)
# I have also tried
# #user.public_email = true
# and
# #user.toggle!(public_email)
#user.save
puts "EMAIL IS #{#user.public_email}"
get :show, :id => #user.id
response.should have_selector("dt", :content => "Email")
end
Rspec will print that "EMAIL IS true" but in the view, public_email is not true (I have <%= "User email is #{#user.public_email}." %> and that prints false).
All my other tests work as expected and it works fine in development.
Why is this happening?
EDIT:
This is my own fault. I was trying to avoid writing the line get :show, :id => #user.id a bunch of times, so I had another before(:each) do call after those tests, and I thought by listing it after them it would not count, but apparently it does.
I don't understand how to test with rspec and internationalization.
For example, in requests tests I do
I18n.available_locales.each do |locale|
visit users_path(locale: locale)
#...
end
and it works just fine: every locale tests correct.
But in mailers this trick doesn't work.
user_mailer_spec.rb
require "spec_helper"
describe UserMailer do
I18n.available_locales.each do |locale|
let(:user) { FactoryGirl.build(:user, locale: locale.to_s) }
let(:mail_registration) { UserMailer.registration_confirmation(user) }
it "should send registration confirmation" do
puts locale.to_yaml
mail_registration.body.encoded.should include("test") # it will return error with text which allow me to ensure that for each locale the test call only :en locale email template
end
end
end
It runs few times (as many as many locales I have), but every time it generate html for the default locale only.
When I call UserMailer.registration_confirmation(#user).deliver from controller, it works fine.
user_mailer.rb
...
def registration_confirmation(user)
#user = user
mail(to: user.email, subject: t('user_mailer.registration_confirmation.subject')) do |format|
format.html { render :layout => 'mailer'}
format.text
end
end
...
views/user_mailer/registration_confirmation.text.erb
<%=t '.thx' %>, <%= #user.name %>.
<%=t '.site_description' %>
<%=t '.credentials' %>:
<%=t '.email' %>: <%= #user.email %>
<%=t '.password' %>: <%= #user.password %>
<%=t '.sign_in_text' %>: <%= signin_url %>
---
<%=t 'unsubscribe' %>
I repeat - it works fine for all locales.
I have the question only about rspec tests for it.
I think you may have to wrap your test in a describe/context block to allow the it block to see your let variables:
require "spec_helper"
describe UserMailer do
I18n.available_locales.each do |locale|
describe "registration" do
let(:user) { FactoryGirl.build(:user, locale: locale.to_s) }
let(:mail_registration) { UserMailer.registration_confirmation(user) }
it "should send registration confirmation" do
puts locale.to_yaml
mail_registration.body.encoded.should include("test")
end
end
# ...
end
# ...
end
As for why, perhaps this StackOverflow answer on let variable scoping may help.
Edit
Is the issue that you've assigned a locale to your user, but you don't pass it into the mail method anywhere? Perhaps this StackOverflow answer would be of reference. Hopefully one of the two answers there would be relevant in your situation. Here's my simple attempt at adapting the first answer there to your situation (untested obviously):
user_mailer.rb
...
def registration_confirmation(user)
#user = user
I18n.with_locale(user.locale) do
mail(to: user.email,
subject: t('user_mailer.registration_confirmation.subject')) do |format|
format.html { render :layout => 'mailer' }
format.text
end
end
end
...
You probably need to specify the locale, as in:
mail_subscribe.body.encoded.should include(t('user_mailer.subscribe_confirmation.stay', locale: locale))
You can also try adding I18n.locale = user.locale right before the mail call in the registration_confirmation method.
I want to test that my controller action is rendering a partial.
I've poked around and I can't seem to find anything that works.
create action:
def create
#project = Project.new...
respond_to do |format|
if #project.save
format.js { render :partial => "projects/form" }
end
end
end
spec:
it "should save and render partial" do
....
#I expected/hoped this would work
response.should render_partial("projects/form")
#or even hopefully
response.should render_template("projects/form")
#no dice
end
If you're looking for a REAL answer... (i.e. entirely in RSpec and not using Capybara), the RSpec documentation says that render_template is a wrapper on assert_template. assert_template (according to the docs) also indicates that you can check that a partial was rendered by including the :partial key.
Give this a go...
it { should render_template(:partial => '_partialname') }
Update see bluefish's answer below, it seems to be the correct answer
Would you consider using Capybara for your integration testing? I found ajax difficult to test with rspec alone. In your case I'm not even sure you are getting a response back yet. In capybara it waits for the ajax call to finish and you can call the page.has_xxxx to see if it was updated. Here is an example:
it "should flash a successful message" do
visit edit_gallery_path(#gallery)
fill_in "gallery_name", :with => "testvalue"
click_button("Update")
page.has_selector?("div#flash", :text => "Gallery updated.")
page.has_content?("Gallery updated")
click_link "Sign out"
end
another great way to test your ajax controller method is to check the assignments which are later used to render the result. Here is a little example:
Controller
def do_something
#awesome_result = Awesomeness.generete(params)
end
JBuilder
json.(#awesome_result, :foo, :bar)
Rspec Controller Test
describe :do_something do
before do
#valid_params{"foo" => "bar"}
end
it "should assign awesome result" do
xhr :post, :do_something, #valid_params
assigns['awesome_result'].should_not be_nil
end
end