Rspec for mailers with I18n - ruby-on-rails

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.

Related

Rails rspec helper methods not recognised in tests

I have a helper method that hides some buttons depending on user role.
#application_controller
helper_method :current_user, :manager_can_see
def manager_can_see(company)
#if manager can see, company owner can see as well
if current_user
return current_user.has_role?(:manager, company) ||
current_user == company.user ||
current_user.has_role?('superuser')
end
end
Then in my view I have this line:
<% if manager_can_see(#appliance.company) %>
...
<% end %>
This works when I visit that page. But it fails in rspec test saying:
ActionView::Template::Error:
undefined method `manager_can_see'
What can be the reason and solution to this?
Here is my test:
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'admin/show', type: :view do
before(:each) do
#user = assign(:user, FactoryBot.create(:user))
#company = assign(:company, FactoryBot.create(:company, user: #user))
10.times do |i|
FactoryBot.create(:appliance, company: #company, name: "Test object #{i}")
end
session[:user_id] = #user.id
#companies = Company.where(user: current_user)
#appliance = assign(:appliance, FactoryBot.create(:appliance))
end
I18n.available_locales.each do |_locale|
it 'renders attributes in <p>' do
render
end
end
end

Rails 6, minitest system test: password reset fails in tests, but works when I manually change it

I'm using Rails 6 and minitest with the built-in system tests (which use Capybara I think) and using FactoryBot as well to generate my test records.
I have a pretty standard password rest feature I'm trying to implement.
I've verified that when I go to the pages in the browser and fill out the information it does indeed change the user's password, but for some reason the password is never changed in the test.
It's almost like the #user object is being cached in the tests and won't reload in the test, but I have no idea why that would be.
Anyone know why this test would fail but the functionality works in "real life" when I manually change a password?
# test/system/password_resets_test.rb
require "application_system_test_case"
class PasswordResetsTest < ApplicationSystemTestCase
test "change password" do
original_password = "password"
new_password = "new-password"
#user = create(:user, password: original_password, password_reset_token_sent_at: Time.current)
visit password_reset_path(#user.password_reset_token)
fill_in "user[password]", with: new_password
click_on "Update Password"
assert_equal(#user.reload.password, new_password)
end
end
# app/views/password_resets/show.html.erb
<%= form_with model: #user, url: password_reset_path(#user.password_reset_token), method: :put do |form| %>
<div class="field">
<%= form.label :password, "Password" %><br />
<%= form.password_field :password, autofocus: true, required: true %>
</div>
<div class="field">
<%= form.submit "Update Password" %>
</div>
<% end %>
# app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
def show
if #user = User.find_by(password_reset_token: params[:id])
if #user.password_reset_token_expired?
flash[:error] = "Your password reset has expired"
redirect_to new_password_reset_path
end
else
flash[:error] = "Invalid password reset token"
redirect_to new_password_reset_path
end
end
def update
#user = User.find_by(password_reset_token: params[:id])
new_password = password_reset_params[:password]
# Automatically set `#password_confirmation` so user does not have
# to enter in password twice on reset page.
if #user&.update(password: new_password, password_confirmation: new_password)
let_user_in(#user)
else
render :show
end
end
private
def password_reset_params
params.require(:user).permit(:password)
end
# app/models/user.rb
class User < ApplicationRecord
PASSWORD_RESET_TIME_LIMIT_IN_HOURS = 4.freeze
has_secure_password
has_secure_token :password_reset_token
validates :password,
presence: true,
length: { minimum: 8 },
allow_nil: true
def password_reset_token_expired?
return true if password_reset_token_sent_at.blank?
password_reset_token_sent_at < PASSWORD_RESET_TIME_LIMIT_IN_HOURS.hours.ago
end
end
click_on doesn't guarantee any actions triggered by the click have happened when it returns. This is because Capybara has no way of knowing what (if any) actions would have been triggered by that click. This means your assertion of the new password is probably happening before the page has even submitted. To fix that you need to use one of the Capybara provided retrying assertions (which assert_equal is not) to check for something visible on the page that indicates the update has occurred.
Something along the lines of
click_on "Update Password"
assert_text "Password Updated!" # whatever message your page shows to indicate successful password update
assert_equal(#user.reload.password, new_password)
should fix your issue.

rails, rspec: No flash message on page, when testing feature

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

Rspec test requires sleep to work

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.

Rails url helpers not working in Rspec

I am new to programming, this is my first application.
While creating an application in Rails, i have two models. User&list,nested.
resources :users do
resources :lists
end
These are the following routes i obtain with this setting:
user_lists GET /users/:user_id/lists(.:format) lists#index
POST /users/:user_id/lists(.:format) lists#create
new_user_list GET /users/:user_id/lists/new(.:format) lists#new
edit_user_list GET /users/:user_id/lists/:id/edit(.:format)lists#edit
user_list GET /users/:user_id/lists/:id(.:format) lists#show
PUT /users/:user_id/lists/:id(.:format) lists#update
DELETE /users/:user_id/lists/:id(.:format) lists#destroy
With regards i have created the views with the following links.
<div class="stats">
<a href="<%= user_lists_path(current_user) %>">
<%= pluralize(current_user.lists.count, 'List') %>
</a>
</div>
<div class="list">
<%= link_to 'Create List', new_user_list_path(current_user) %>
</div>
These work as expected, however when i use the same url helpers in testing i get an error.
describe "List create page" do
let(:user) { FactoryGirl.create(:user) }
before do
user.save
visit new_user_list_path(user)
end
it { should have_selector('title', text: 'SocialTask | List') }
it { should have_selector('h1', text: 'Create list') }
describe "invalid list creation" do
before { click_button 'Create list' }
it { should have_content('Error in creating list') }
end
end
This causes the tests to have an error.
Failure/Error: visit new_user_list_path(user)
NoMethodError:
undefined method `lists' for nil:NilClass
I have tried playing around with the url that did not work.
I tried updating rspec/capybara that did not work either.
I have also checked the inclusion of
config.include Rails.application.routes.url_helpers
in the spec helper.
How do i get the helpers to work? Or am i missing some minor detail?
Thanks in advance.
Helper Methods.
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def signed_in?
!current_user.nil?
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def current_user?(user)
current_user == user
end
end
The rspec helper to sign in.
support/utilities.rb
include ApplicationHelper
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
cookies[:remember_token] = user.remember_token
end
Without seeing the stack trace, I think your problem is in the view on this line:
<%= pluralize(current_user.lists.count, 'List') %>
It seems like current_user is nil. Normally you should define some kind of helper method in your RSpec suite to simulate a user logging in. That way, current_user will return the user that you stub out in the test.
Here's an example:
# spec/support/session_helper.rb
module SessionHelper
def login(username = 'admin')
request.session[:user_id] = User.find_by_username(username).id
end
end
Yours will differ depending on how you authenticate your users. For example, Devise publishes its own set of test helpers, so you can simply include its helpers directly:
# spec/support/devise.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
Seems it's getting an error because the user doesn't exists. Try to change user.save to user.save! then you'll catch the error on creation I think..

Resources