In the past, Rails 3, I've integrated action mailer testing with my Cucumber/Rspec-Capybara tests like the following example. With Rails 4, using the following doesn't seem to work.
I am able to see that the job is queued using enqueued_jobs.size. How do I initiate the enqueued job to ensure that the email subject, recipient and body are correctly being generated?
app/controllers/robots_controller.rb
class MyController < ApplicationController
def create
if robot.create robot_params
RobotMailer.hello_world(robot.id).deliver_later
redirect_to robots_path, notice: 'New robot created'
else
render :new
end
end
end
spec/features/robot_spec.rb
feature 'Robots' do
scenario 'create a new robot' do
login user
visit '/robots'
click_link 'Add Robot'
fill_in 'Name', with: 'Robbie'
click_button 'Submit'
expect(page).to have_content 'New robot created'
new_robot_mail = ActionMailer::Base.deliveries.last
expect(new_robot_mail.to) eq 'myemail#robots.com'
expect(new_robot_mail.subject) eq "You've created a new robot named Robbie"
end
end
I use the rspec-activejob gem and the following code:
RSpec.configure do |config|
config.include(RSpec::ActiveJob)
# clean out the queue after each spec
config.after(:each) do
ActiveJob::Base.queue_adapter.enqueued_jobs = []
ActiveJob::Base.queue_adapter.performed_jobs = []
end
config.around :each, perform_enqueued: true do |example|
#old_perform_enqueued_jobs = ActiveJob::Base.queue_adapter.perform_enqueued_jobs
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
example.run
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = #old_perform_enqueued_jobs
end
config.around :each, perform_enqueued_at: true do |example|
#old_perform_enqueued_at_jobs = ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
example.run
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = #old_perform_enqueued_at_jobs
end
end
This lets me tag features/scenarios with perform_enqueued: true if I want the jobs to actually execute.
Related
I'm implementing in my first project the crud operation "delete", but the system take the command as a "get", whitout errors.
this is the controller:
class MoviesController < ApplicationController
def index
#movies = Movie.all
end
#GET /movies/:id
def show
id = params[:id]
#movie = Movie.find(id)
#render plain: 'show'+params[:id]
end
#POST /movies/
# skip_before_action :verify_autenticity_token:
def create
#movie = Movie.create!(params[:movie].permit(:title, :rating, :description, :realease_date))
flash[:notice] = "#{#movie.title} was successfully created."
redirect_to movies_path
end
#new
def new
end
def edit
id = params[:id]
#movie = Movie.find(id)
end
def update
id = params[:id]
#movie = Movie.find(id)
if #movie.update_attributes!(params[:movie].permit(:title,:rating,:realease_date))
flash[:notice] = "#{#movie.title} has been edited."
redirect_to movies_path
end
end
def destroy
id = params[:id]
#movie = Movie.find(id)
#movie.destroy
flash[:notice] = "#{#movie.title} has been deleted."
redirect_to movies_path
end
end
this is the view where is possible call "Delete" operation:
%h2 Details about #{#movie.title}
%ul#details
%li
Rating:
= #movie.rating
%li
Released on:
= #movie.realease_date#.strftime("%B %d, %Y")
%h3 Description:
%p#description= #movie.description
%h4 Reviews:
- if #movie.reviews.empty?
%p
No reviews for this movie...
-else
- #movie.reviews.each do |r|
- u = Moviegoer.find(r.moviegoer_id)
<b>#{r.vote}</b> (<i>#{Moviegoer.find(r.moviegoer_id).name}</i>) #{r.message} <br />
= link_to 'Add review', new_movie_review_path(#movie)
<br/><br/>
#{link_to 'Edit info', edit_movie_path(#movie)} - #{link_to 'Delete', movie_path(#movie), :method => :delete} - #{link_to 'Back to movie list', movies_path}
The problem probably is in the last line, because in server command line "Delete" isn't calls.
this is the server response (is the last GET that is execute when i press "Delete" on the browser) :
enter image description here
file routes.rb:
Rails.application.routes.draw do
resources :movies do
resources :reviews, only: [:new, :create, :destroy]
end
resources :moviegoers
root :to => redirect('/movies')
end
Result of command "rake routes":
Prefix Verb URI Pattern Controller#Action
movie_reviews POST /movies/:movie_id/reviews(.:format) reviews#create
new_movie_review GET /movies/:movie_id/reviews/new(.:format) reviews#new
movie_review DELETE /movies/:movie_id/reviews/:id(.:format) reviews#destroy
movies GET /movies(.:format) movies#index
POST /movies(.:format) movies#create
new_movie GET /movies/new(.:format) movies#new
edit_movie GET /movies/:id/edit(.:format) movies#edit
movie GET /movies/:id(.:format) movies#show
PATCH /movies/:id(.:format) movies#update
PUT /movies/:id(.:format) movies#update
DELETE /movies/:id(.:format) movies#destroy
moviegoers GET /moviegoers(.:format) moviegoers#index
POST /moviegoers(.:format) moviegoers#create
new_moviegoer GET /moviegoers/new(.:format) moviegoers#new
edit_moviegoer GET /moviegoers/:id/edit(.:format) moviegoers#edit
moviegoer GET /moviegoers/:id(.:format) moviegoers#show
PATCH /moviegoers/:id(.:format) moviegoers#update
PUT /moviegoers/:id(.:format) moviegoers#update
DELETE /moviegoers/:id(.:format) moviegoers#destroy
root GET / redirect(301, /movies)
rails_service_blob GET /rails/active_storage/blobs/:signed_id /*filename(.:format) active_storage/blobs#show
rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
rails_direct_uploads POST /rails/active_storage /direct_uploads(.:format) active_storage/direct_uploads#create
It's hard to say exactly what's going on without seeing the corresponding routes. You might try running rake routes to see what the available routes are.
Based on the comments in your controller, I'm guessing you probably have to do something like pluralize the path link_to 'Delete', movies_path(id: #movie.id), method: :delete, but that would only fix the problem if your resource routes are misconfigured.
In order for the link to work as you have it configured your route file would either have to contain resources :movies, or a specially configured route to map to the delete action.
In an edit form, an existing password appears in the form as blank, which seems to be the default Rails behavior. I'm trying, however, to avoid the password (or in my case passwords) from being updated to blank if a new password isn't entered, similar to what is described here:
Rails Activerecord update saving unedited field as blank
The difference for me is that the password field is more deeply nested and there is more than one.
Basically what I have is a small bank transfer app where for every :transfer there are two :transfer_accounts, source and destination (transfer_accounts is a "has_many, through" join table for transfers and accounts) and both transfer accounts have an :account with a :password attribute.
My attempt was something like this at the top of the update action:
params[:transfer][:transfer_accounts_attributes].each do |k, v|
v[:account_attributes][:password].delete if v[:account_attributes][:password].empty?
end
which didn't work. Either password left blank is updated to blank.
How would I iterate through the params and prevent either or both passwords from updating if they are left blank?
Here is my controller:
class TransfersController < ApplicationController
def new
#transfer = Transfer.new
#transfer.transfer_accounts.build(account_transfer_role: 'source').build_account
#transfer.transfer_accounts.build(account_transfer_role: 'destination').build_account
#valid_banks = Bank.all.collect {|c| [c.name, c.id]} # available banks seeded in database
end
def index
#transfers = Transfer.all
end
def show
#transfer = resource
end
def create
#transfer = Transfer.new(transfer_params)
if #transfer.save
redirect_to transfers_path, notice: "Transfer Created"
else
redirect_to transfers_path, alert: "Transfer Not Created"
end
end
def edit
resource
#valid_banks = Bank.all.collect {|c| [c.name, c.id]} # available banks seeded in database
end
def update
if resource.update_attributes(transfer_params)
redirect_to transfers_path(resource), notice: "Transfer Updated"
else
redirect_to edit_transfer_path(resource), alert: "Transfer Not Updated"
end
end
def destroy
resource.destroy
end
private
def resource
#transfer ||= Transfer.find(params[:id])
end
def transfer_params
params.require(:transfer).
permit(:name, :description,
transfer_accounts_attributes:
[:id, :account_transfer_role,
account_attributes:
[:id, :bank_id, :name, :description, :user_name,
:password, :routing_number, :account_number
]
])
end
end
params[:transfer][:transfer_accounts_attributes].each do |k, v|
v[:account_attributes].delete(:password) if v[:account_attributes][:password].blank?
end
You have to call hash.delete, rather than delete the contents of the already blank value. Also .blank? is your friend, since that will take care of nil and == ''.
I'm having a bizarre problem where I create an admin user for an Rspec/Capybara test and the user is mysteriously deleted after logging in. When I run my test, the user is created and successfully logged in. However when Capybara visits the admin_categories_path, the test fails. Rails raises an exception because current_user is not defined. When I insert a binding.pry, I can see that the user exists up until they are logged in, at which point the user disappears from the test database, causing current_user to be undefined, and thereby triggering an exception. I'm at a loss as to how/why this is happening.
UPDATE: I've gotten past the problem of the user being deleted. Now when Capybara sees the admin_categories_path page, category isn't displayed. Inserting a binding.pry reveals that category is present in the database.
When I launch the app and log in manually, I have no problems accessing admin_categories_path.
Here is my spec file:
require 'rails_helper'
feature 'admin edits category', %Q{
As an admin, I want to edit a category or subcategory, so that it better
represents the content under it.
Acceptance Criteria:
* [X] - I can edit the name of a category inline.
* [X] - I can edit the name of a subcategory inline.
} do
let(:admin) do
FactoryGirl.create(:user, admin: true)
end
let(:category) do
FactoryGirl.create(:category)
end
scenario 'admin edits category title', js: true, focus: true do
login_as(admin)
visit admin_categories_path
bip_area category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
end
Here is my helper file authentication.rd:
module Helpers
module Authentication
def log_in_as(user)
visit new_user_session_path
within "#new_user" do
fill_in 'user[email]', with: user.email
fill_in 'user[password]', with: user.password
click_on "Log in"
end
end
end
RSpec.configure do |config|
config.include Authentication, :type => :feature
end
end
Here is my user factory:
require 'factory_girl'
FactoryGirl.define do
factory :user do
sequence(:email) {|n| "lafiel.abriel#{n}#abhnation.com" }
username
password 'password'
password_confirmation 'password'
end
factory :category do
sequence(:name) { |n| "Category ##{n}" }
parent_id nil
display_index 1
user
end
sequence :username do |n|
"Lafiel_Abriel_#{n}"
end
end
And here is my application_controller.rd where the exception is triggered:
module Admin
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :reject_unless_admin
layout 'admin/layouts/application'
protect_from_forgery with: :exception
helper :avatar, :devise, :admin
protected
def reject_unless_admin
unless current_user && current_user.admin?
raise ActionController::RoutingError.new('404: Not Found')
end
end
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << [
:avatar,
:remote_avatar_url,
:email,
:username,
:first_name,
:last_name,
:age,
:website,
:password,
:password_confirmation,
:current_password
]
end
end
end
Any help/insight is greatly appreciated. Thanks.
If I recall correctly, this problem had something to do with the test database schema being out of sync with the development database. Try running rake db:test:prepare to ensure your test schema matches your development schema.
In my specific case, I had a two problems to overcome.
The first problem occurred when I created a category and an admin in a let block. Those needed to be placed in a before block so that they were created before the test executed. When I placed those variables in let blocks, they were not being created until the first time they were called in the test. So when I logged in as an admin, the admin variable was being passed to my login_as helper method before it was saved to the database. The same is true for the category variable.
This code doesn't work for me:
let(:admin) do
FactoryGirl.create(:user, admin: true)
end
let(:category) do
FactoryGirl.create(:category)
end
scenario 'admin edits category title', js: true, focus: true do
login_as(admin)
visit admin_categories_path
bip_area category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
This is solution I used to overcome the first problem:
before(:each) do
#admin = FactoryGirl.create(:user, admin: true)
#category = FactoryGirl.create(:category, user: #admin)
end
scenario 'admin edits category title', js: true do
skip "Doesn't work yet."
login_as(#admin)
visit admin_categories_path
wait_for_ajax
bip_text #category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
The second problem is that when my test hits the admin/categories_controller.rb, Category.all returns an empty array, even though #category.save! and #category.valid? return true when I test my before block. I was never able to find a solution to the problem and ended up putting the test on ice.
I think part of the problem is that the documentation for the Best-in-Place gem is not very good. I used it at the time because it was an easy solution to my problem. Going forward though, I would use something like angular for this use-case. The documentation is much better and it's not a black box like a lot of gems are.
I'm not sure what bip_area does, but since your test says that it's editing a category I assume you are expecting the category to be present on the page when you visit admin_categories_path. When you visit that path however category has not yet been created since you are using 'let' which is lazily evaluated (created at time of first use of the variable), so it would not be shown on the screen for you to edit. Using binding.pry and then looking at that variable would actually create it, so it might be confusing you into thinking it was there. You can use 'let!' instead which will force the variable to be created before each test instead of lazily evaluating it.
I'm using authlogic for my user authentication and in my ApplicationController I have "current_user", "current_user_session", etc. defined and set as helper_methods.
I have an extremely simple view spec for my main index:
RSpec.describe "main/index.html.erb", :type => :view do
context "when not logged in" do
before do
allow(view).to receive(:current_user).and_return(nil)
end
it "has an h1" do
render
expect(rendered).to include('h1')
end
end
end
The problem is that if "mocks.verify_partial_doubles = true" in my config then this causes an impressively massive error as it dumps an entire object and then says at the bottom:
1) main/index.html.erb when not logged in has an h1
Failure/Error: allow(view).to receive(:current_user).and_return(nil)
#<#<Class:0x00000104c249d0>:.........
#rendered_views={}>> does not implement: current_user
Of course, it is recommended that verify_partial_doubles is set to true, but in doing so this breaks. I pulled this straight from the documentation:
https://www.relishapp.com/rspec/rspec-rails/v/3-1/docs/view-specs/view-spec#passing-view-spec-that-stubs-a-helper-method
If the method appears in ApplicationHelper it'll work. But if it's in ApplicationController and defined as a helper_method there's no such luck:
helper_method :current_user, ...
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.record
end
I want the protection that verify_partial_doubles provides, how can I work around this?
This is a known issue and the only way to get it working is to extract the methods into a module and include it in your view helpers and the controller.
More information at: https://github.com/rspec/rspec-rails/issues/1076
You can disable double verification for views as follows:
RSpec.configure do |config|
config.before(:each, type: :view) do
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = false
end
end
config.after(:each, type: :view) do
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
end
This way you'll be able to keep stubbing view methods with:
allow(view).to receive(:current_user).and_return(nil)
More information at: https://github.com/rspec/rspec-rails/issues/1076
I've just implemented OmniAuth (using Ryan Bates' Screencast http://asciicasts.com/episodes/235-omniauth-part-1) and am writing Rspec tests for the functionality and have ran into trouble testing the authentifications#create action. I'm at quite a loss as to how to test this one -- in particular how to stub the local variable omniauth. No matter what I try I keep can't get any tests to work.
Taking a cut down version of the action, how would you test that a new is called on User for example
#cut down version of the authentifications controller code I am attempting to test
def create
omniauth = request.env["omniauth.auth"]
authentification = Authentification.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
....
user = User.new
....
end
#example test
it "should create a new user" do
subject.stub_chain(:request,:env) {{"omniauth.auth" => {'provider' =>1, 'uid' => 2}}}
User.should_receive(:new)
post :create
end
I Did that :
class SessionsController < ApplicationController
def create
#user = User.find_by_auth_hash(auth_hash)
end
def auth_hash
request.env['omniauth.auth']
end
end
describe SessionsController do
it 'should allow login' do
controller.stub!(:auth_hash).and_return({'provider' => 'twitter', 'uid' => '1234'})
get :create, :provider => 'twitter'
assigns(:user).should_not be_nil
end
end
Hope that helps.