I have the following RSpec test for my UserMailer class:
require "spec_helper"
describe UserMailer do
it "should send welcome emails" do
ActionMailer::Base.deliveries.should be_empty
user = Factory(:user)
UserMailer.welcome_email(user).deliver
ActionMailer::Base.deliveries.should_not be_empty
end
end
This test passed the first time, but failed the second time I ran it. After doing a little bit of debugging, it appears that the 1st test added an item to the ActionMailer::Base.deliveries array and that item never got cleared out. That causes the first line in the test to fail since the array is not empty.
What's the best way to clear out the ActionMailer::Base.deliveries array after an RSpec test?
RSpec.describe UserMailer do
before do
# ActionMailer::Base.deliveries is a regular array
ActionMailer::Base.deliveries = []
# or use ActionMailer::Base.deliveries.clear
end
it "sends welcome email" do
user = create(:user)
UserMailer.welcome_email(user).deliver_now
expect(ActionMailer::Base.deliveries).to be_present
end
end
You can clear the deliveries after each test quite easily, adding this into your spec_helper.rb.
RSpec.configure do |config|
config.before { ActionMailer::Base.deliveries.clear }
end
I'd suggest reading my article about the correct emails configuration in Rails where I talk also about testing them correctly.
As Andy Lindeman points out, clearing the deliveries is done automatically for mailer tests. However, for other types, simply add , :type => :mailer to the wrapping block to force the same behavior.
describe "tests that send emails", type: :mailer do
# some tests
end
Related
I'm migrating an app from rails 4 to rails 6 and the test are now broken.
I'm not so good in testing, i must improve, but I cannot understand why a before block run after the test.
require 'rails_helper'
RSpec.describe Admin::TrainingEmailsController, type: :controller do
describe "GET #create", :uses_mail do
context "with valid attributes" do
before(:each) do
binding.pry
end
describe "sends the value letter request email" do
binding.pry
it { expect(1+1).to eq(2) }
end
end
end
end
I add a couple of pry, and the when I run rspec "sends the value letter request email" run before the before block and so my ActionMailer::Base.deliveries.last is empty
I try with before, before(:all) and before(:each)
It doesn't.
describe block is not running a test yet. It's just defining it.
only it or specify blocks, when run are executing a test. Put binging.pry inside the it block to see it:
describe "sends the value letter request email" do
binding.pry
let(:mail) { ActionMailer::Base.deliveries.last }
let(:request_sender) { "ValueLetters#vl.balancedview.org" }
let(:accepted_participants_emails) do
training.participants.accepted.
map(&:email)
end
it do
binding.pry # <-- here
expect(mail.to).to match(accepted_participants_emails) }
end
end
My ProductCategory spec:-
require 'rails_helper'
RSpec.describe ProductCategory, type: :model do
before(:each) do
#product_category = create(:product_category)
end
context "validations" do
it "should have valid factory" do
expect(#product_category).to be_valid
end
it "should have unique name" do
product_category_new = build(:product_category, name: #product_category.name)
expect(product_category_new.save).to be false
end
end
end
The spec runs fine, but when I use before(:all) instead of before(:each), second example fails -
expected false got true I know the difference between before(:all) and before(:each) but I am not able to find the exact reason why second example fails with before(:all)
before :all only runs once before all the examples, so the #product_category is created once. If you have a something like a DatabaseCleaner truncation running after each test, the record is no longer in the database in the second test, thus passing the validation.
before :each on the other hand will be run before each example, so the record will be there in the second example even if the database was cleaned in the meantime.
I set up a simple controller with its according feature_spec:
stamps_controller.rb
class StampsController < ApplicationController
def new
#stamp = Stamp.new
end
def create
#stamp = Stamp.new(stamp_params)
if #stamp.save
redirect_to(stamp_url(#stamp.id), status: 201)
else
render 'new'
end
end
def show
#stamp = Stamp.find(params[:id])
end
private
def stamp_params
params.require(:stamp).permit(::percentage)
end
end
specs/requests/stamps_request_spec.rb
RSpec.describe 'stamp requests', type: :request do
describe 'stamp creation', js: true do
before do
FactoryBot.create_list(:domain, 2)
FactoryBot.create_list(:label, 2)
end
it 'allows users to create new stamps' do
visit new_stamp_path
expect(page).to have_content('Percentage')
find('#stamp_percentage').set('20')
click_button 'Create'
expect(current_path).to eq(stamp_path(Stamp.first.id))
end
end
end
According to the capybara docs:
Capybara automatically follows any redirects, and submits forms associated with buttons.
But this does not happen in the test, instead it throws an error:
expected: "/stamps/1
got: "/stamps"
The results are obvious: it successfully creates the stamp but fails to redirect to the new stamp. I also confirmed this by using binding.pry.
Why wouldn't capybara follow the redirect as described in the docs?
Sidenotes:
it even fails if I use the normal driver instead of js
I've looked into lots of SO questions and docs, finding nothing useful. One potential attempt I was unable to grasp was an answer with no specifics of how to implement it.
my configs:
support/capybara.rb
require 'capybara/rails'
require 'capybara/rspec'
Capybara.server = :puma
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, browser: :firefox, marionette: true)
end
Capybara.javascript_driver = :selenium
RSpec.configure do |config|
config.include Capybara::DSL
end
spec_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'
require 'factory_bot_rails'
require 'pundit/matchers'
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
# .
# unrelated stuff
# .
end
You have a number of issues in your test.
First, Capybara is not meant to be used in request specs - https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec - but should instead be used with feature/system tests. Once you've fixed that you should no longer need to include Capybara into every RSpec test and should remove the config.include Capybara::DSL from you config (when you require capybara/rspec it includes Capybara into the test types it should be included in - https://github.com/teamcapybara/capybara/blob/master/lib/capybara/rspec.rb#L10)
Second, click_button is not guaranteed to wait for any actions it triggers to complete. Because of that you need to wait for a visual page change before attempting to access any database objects that would be created by that action (technically you really shouldn't be doing direct DB access in feature specs at all but if you're going to ...)
click_button 'Create'
expect(page).to have_text('Stamp created!') # Whatever message is shown after creation
# Now you can safely access the DB for the created stamp
Third, as pointed out by #chumakoff you should not be using static matchers with Capybara and should instead be using the matchers provided by Capybara
click_button 'Create'
expect(page).to have_text('Stamp created!') # Whatever message is shown after creation
expect(page).to have_path(stamp_path(Stamp.first.id))
Finally, you should look at your test.log and use save_and_open_screenshot to see what your controllers actually did - It's and there's actually an error being raised on creation which is causing your app to redirect to /stamps and display the error message (would also imply your test DB isn't actually being reset between tests, or the factories you show are creating nested records, etc).
Update: After rereading your controller code I noticed the that you're passing a 201 status code to redirect_to. 201 won't actually do a redirect - From the redirect_to docs - https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-redirect_to
Note that the status code must be a 3xx HTTP code, or redirection will
not occur.
The problem might be that it takes some time for current_path to change after the form is submitted. Your code would work if you put sleep(x) before expect(current_path).
Instead, you should use methods that have so-called "waiting behaviour", such as has_current_path?, have_current_path, assert_current_path:
expect(page).to have_current_path(stamp_path(Stamp.first.id))
or
expect(page.has_current_path?(stamp_path(Stamp.first.id))).to eq true
For anyone else coming here, another possible solution is to increase your wait time. Either globally or per click
# Globally
Capybara.default_max_wait_time = 5
# Per Click
find("#my-button").click(wait: 5)
I'm doing a test using RSPEC, and used Sidekiq for background jobs.
Since, there's no generator for workers in rspec, not sure what to use.
https://relishapp.com/rspec/rspec-rails/docs/generators
require 'spec_helper'
RSpec.describe TestWorker, type: ? do # ex. :worker :sidekiq ...
describe "TestWorker" do
it "" do
....
end
end
end
bundle exec rspec spec/workers/test_worker_spec.rb
Doing like below, i'm getting: uninitialized constant TestWorker
require 'spec_helper'
describe TestWorker do
it "" do
....
end
end
As i tried, gem rspec-sidekiq
https://github.com/philostler/rspec-sidekiq
Can someone provide a sample template for testing app/workers/ in Rspec.
Thanks.
I have not used the rspec-sidekiq gem, however, here is an example of how I am checking for Background jobs which uses sidekiq
# app/spec/workers/demo_worker_spec.rb
require 'rails_helper'
require 'sidekiq/testing'
Sidekiq::Testing.fake!
RSpec.describe DemoWorker, type: :worker do
describe "Sidekiq Worker" do
let (:demo) { FactoryGirl.create(:demo) }
it "should respond to #perform" do
expect(DemoWorker.new).to respond_to(:perform)
end
describe "Demo" do
before do
Sidekiq::Extensions.enable_delay!
Sidekiq::Worker.clear_all
end
it "should enqueue a Email and SMS job" do
assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
Mailer.delay.demo_request(demo.id)
assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
end
end
end
end
As of I'm checking, if the instance responds to perform.
Then, I'm asserting before and after the job is scheduled.
You might sometimes want to test more than just the fact that the worker has been enqueud or not.
While it is better to decouple complex stuff that could happen in a worker's perform block, it can be tested as a standard class :
it { expect { MyWorker.new.perform }.to change { ...expectations... } }
or
it do
MyWorker.new.perform
... expectations ..
end
I have started my journey with TDD in Rails and have run into a small issue regarding tests for model validations that I can't seem to find a solution to. Let's say I have a User model,
class User < ActiveRecord::Base
validates :username, :presence => true
end
and a simple test
it "should require a username" do
User.new(:username => "").should_not be_valid
end
This correctly tests the presence validation, but what if I want to be more specific? For example, testing full_messages on the errors object..
it "should require a username" do
user = User.create(:username => "")
user.errors[:username].should ~= /can't be blank/
end
My concern about the initial attempt (using should_not be_valid) is that RSpec won't produce a descriptive error message. It simply says "expected valid? to return false, got true." However, the second test example has a minor drawback: it uses the create method instead of the new method in order to get at the errors object.
I would like my tests to be more specific about what they're testing, but at the same time not have to touch a database.
Anyone have any input?
CONGRATULATIONS on you endeavor into TDD with ROR I promise once you get going you will not look back.
The simplest quick and dirty solution will be to generate a new valid model before each of your tests like this:
before(:each) do
#user = User.new
#user.username = "a valid username"
end
BUT what I suggest is you set up factories for all your models that will generate a valid model for you automatically and then you can muddle with individual attributes and see if your validation. I like to use FactoryGirl for this:
Basically once you get set up your test would look something like this:
it "should have valid factory" do
FactoryGirl.build(:user).should be_valid
end
it "should require a username" do
FactoryGirl.build(:user, :username => "").should_not be_valid
end
Here is a good railscast that explains it all better than me:
UPDATE: As of version 3.0 the syntax for factory girl has changed. I have amended my sample code to reflect this.
An easier way to test model validations (and a lot more of active-record) is to use a gem like shoulda or remarkable.
They will allow to the test as follows:
describe User
it { should validate_presence_of :name }
end
Try this:
it "should require a username" do
user = User.create(:username => "")
user.valid?
user.errors.should have_key(:username)
end
in new version rspec, you should use expect instead should, otherwise you'll get warning:
it "should have valid factory" do
expect(FactoryGirl.build(:user)).to be_valid
end
it "should require a username" do
expect(FactoryGirl.build(:user, :username => "")).not_to be_valid
end
I have traditionally handled error content specs in feature or request specs. So, for instance, I have a similar spec which I'll condense below:
Feature Spec Example
before(:each) { visit_order_path }
scenario 'with invalid (empty) description' , :js => :true do
add_empty_task #this line is defined in my spec_helper
expect(page).to have_content("can't be blank")
So then, I have my model spec testing whether something is valid, but then my feature spec which tests the exact output of the error message. FYI, these feature specs require Capybara which can be found here.
Like #nathanvda said, I would take advantage of Thoughtbot's Shoulda Matchers gem. With that rocking, you can write your test in the following manner as to test for presence, as well as any custom error message.
RSpec.describe User do
describe 'User validations' do
let(:message) { "I pitty da foo who dont enter a name" }
it 'validates presence and message' do
is_expected.to validate_presence_of(:name).
with_message message
end
# shorthand syntax:
it { is_expected.to validate_presence_of(:name).with_message message }
end
end
A little late to the party here, but if you don't want to add shoulda matchers, this should work with rspec-rails and factorybot:
# ./spec/factories/user.rb
FactoryBot.define do
factory :user do
sequence(:username) { |n| "user_#{n}" }
end
end
# ./spec/models/user_spec.rb
describe User, type: :model do
context 'without a username' do
let(:user) { create :user, username: nil }
it "should NOT be valid with a username error" do
expect(user).not_to be_valid
expect(user.errors).to have_key(:username)
end
end
end