Exceedingly strange ruby '||=' and rspec doubles behavior - ruby-on-rails

Running the following rspec gem versions:
* rspec (2.14.1)
* rspec-core (2.14.7)
* rspec-expectations (2.14.5)
* rspec-mocks (2.14.6)
* rspec-rails (2.14.1)
And octokit 2.7.1
Given the following in spec/support/octokit.rb:
# This support package contains modules for octokit mocking
module OctokitHelper
def mock_pull_requests_for_org org
mock_org_repos(org).map do |repo|
mock_pull_requests repo
end.flatten
end
def mock_org_repos org
##repos ||= [
double('Sawyer::Resource', name: 'repo 1'),
double('Sawyer::Resource', name: 'repo 2')
]
Octokit.stub(:org_repos) { |org=org| ##repos }
##repos
end
def mock_pull_requests repo, gh_handle=nil
##pulls ||= [
double('Sawyer::Resource', title: 'pr 1'),
double('Sawyer::Resource', title: 'pr 2')
]
Octokit.stub(:pull_requests) { |repo| ##pulls }
##pulls
end
end
RSpec.configure do |config|
config.include OctokitHelper
end
Whenever I attempt to call mock_org_repos the first time from a spec, ##repos.first.name is repo 1. The second time around, ##repos.first.name throws a really bizarre error:
1) StudentsController GET #submissions renders pull requests template
Failure/Error: get :submissions, student_id: 2
Double "Sawyer::Resource" received unexpected message :name with (no args)
An example of my usage of mock_org_repos:
require 'spec_helper'
describe StudentsController do
describe 'GET #submissions' do
before do
#user = FactoryGirl.create(:instructor)
#user.stub(:is_instructor?) { true }
#submissions = mock_pull_requests_for_org ENV['GA_ORG_NAME']
controller.stub(:current_user) { #user }
get :submissions, student_id: 2
end
it 'assigns #submissions' do
assigns(:submissions).should == #submissions
end
it 'renders pull requests template' do
response.should render_template 'submissions'
end
end
end

My guess is that one of the doubles you've created in mock_pull_requests with the same name is getting sent :name, but you can check that by making sure your doubles have unique names (i.e. the first argument to double).

Many thanks to rsofaer whom had lent me his second pair of eyes to resolve this, I would like to point out the now very very obvious problem:
Methods stubbed on doubles are 'cleared' every time examples are run, when the stubs are referenced via class variables.
This is a very clear explanation of how RSpec works under the covers.

Related

Getting wrong number of arguments calling a function with keyword arguments With Ruby 3.2

I updated the Ruby version from 2.7.3 to 3.2.0 in my project and some tests started to fail. Among those is one that is calling a function with keyword arguments. This is the error it's throwing:
1) NotificationsMailer notify Sets body
Failure/Error:
def notify(recipient_emails:, subject:, body:)
#body = body
#subject = subject
mail(bcc: recipient_emails, subject: #subject, reply_to: VENDOR_SUPPORT_EMAIL)
end
ArgumentError:
wrong number of arguments (given 1, expected 0; required keywords: recipient_emails, subject, body)
# ./app/mailers/notifications_mailer.rb:5:in `notify'
# /usr/local/bundle/gems/actiontext-6.1.6/lib/action_text/rendering.rb:20:in `with_renderer'
# /usr/local/bundle/gems/actiontext-6.1.6/lib/action_text/engine.rb:59:in `block (4 levels) in <class:Engine>'
# ./spec/mailers/notifications_mailer_spec.rb:16:in `block (3 levels) in <top (required)>'
This is the definition of the function being called:
class NotificationsMailer < ApplicationMailer
VENDOR_SUPPORT_EMAIL = "vendor-support#saatvamattress.com"
def notify(recipient_emails:, subject:, body:)
#body = body
#subject = subject
mail(bcc: recipient_emails, subject: #subject, reply_to: VENDOR_SUPPORT_EMAIL)
end
end
And this is the test file:
require "rails_helper"
RSpec.describe NotificationsMailer, type: :mailer do
describe 'notify' do
let(:recipient_emails) { %w[test#mail.com test2#mail.com] }
let(:mail) do
NotificationsMailer.notify(
recipient_emails: recipient_emails,
subject: 'subject',
body: 'body'
)
end
it 'Sets body' do
expect(mail.body.encoded).to match 'body'
end
it 'Sets subject' do
expect(mail.subject).to eq 'subject'
end
it "Does not set To" do
expect(mail.to).to be_nil
end
it "Does not set CC" do
expect(mail.cc).to be_nil
end
it "Sets BCC with recipients' emails" do
expect(mail.bcc).to eq ['test#mail.com', 'test2#mail.com']
end
it 'Sets reply_to with vendor support email' do
expect(mail.reply_to).to eq ['vendor-support#saatvamattress.com']
end
end
end
I know ruby 3 introduced some changes to keyword arguments, but as I'm calling the function with the arguments in the same order and specifying all the keywords, I don't see where my problem is.
Following some threads I tried sending the arguments in a hash and some other things that weren't so promising, but still getting the error, no clue of what's happening there.
I was able to reproduce your issue with Rails 6.1.7.2 and Ruby 3.2.1. The problem is that Rails 6.1 and Ruby 3.2 aren't fully compatible. There are efforts to backport 3.2 compatibility fixes to Rails 6.1 (see https://github.com/rails/rails/pull/46895) but they haven't been released yet it seems.
You could upgrade to a lower version of Ruby instead (3.1.2 worked fine for me, I'm sure 3.1.3 would too), or you could change your mailer code to be like this:
def notify(recipient_emails, subject, body)
[omitted]
end
(and obviously also change how you call that function).
There's some risk, though, that you'll run into more kwargs related issues with Rails 6.1 and Ruby 3.2, so maybe going with Ruby 3.1 is the better option.

How to proceed with flaky specs after successful bisect

Using Rails 5 and Rspec 3.7 I have a fairly simple test that is currently flapping (sometimes passing sometimes failing). Through the debugging I've done so far it seems that values i'm saving to my test database are not persisting between tests, but I can not figure out why this is the case.
Here is the test suite with comments on the flapping test (the rest consistently pass)
describe ResourceCenterController, type: :controller do
before(:each) do
#platform_instance = FactoryBot.create(:platform_instance)
#domain = FactoryBot.create(:domain, platform_instance: #platform_instance)
#user = FactoryBot.create(:user, platform_instance: #platform_instance, first_name: "O'flaggan")
end
context 'when user IS signed in' do
before(:each) do
login_user(#user)
end
context 'when user in ONE community' do
before(:each) do
#user.communities = [#platform_instance.communities.first]
#user.save!
end
describe '#index' do
before(:each) do
#rc = FactoryBot.create(:resource_center, platform_instance: #platform_instance, launch_at: nil, expire_at: nil)
end
context 'when community assigned NO resource centers' do
before(:each) do
#rc.communities = []
#rc.save!
get :index
end
it_behaves_like '200 w name in body' do
let(:names) { ['There are no files for your review at the moment.'] }
end
end
context 'when community assigned ONE resource center' do
before(:each) do
#rc.communities = [#user.communities.first]
#rc.save!
end
context 'when resource center assigned NO mediafiles' do
before(:each) do
#rc.mediafiles = []
#rc.save!
get :index
end
it_behaves_like '200 w name in body' do
let(:names) { ['There are no files for your review at the moment.'] }
end
end
# this test is flapping
# sometimes it will persist the mediafile and it will show up
# other times it will be saved, why is that?
context 'when resource center assigned ONE mediafile' do
before(:each) do
#mediafile = FactoryBot.create(:mediafile, platform_instance: #platform_instance)
#rc.mediafiles << #mediafile
#rc.save!
get :index
end
it_behaves_like '200 w name in body' do
let(:names) { ["#{#mediafile.name}"] }
end
end
end
end
end
end
end
Here is the shared context
shared_context '200 w name in body' do
it 'returns 200' do
expect(response.status).to eq(200)
end
it 'renders the view' do
names.each do |name|
expect(response.body).to include(name)
end
end
end
Edit: I learned of the bisect flag and ran it with this output
Bisect started using options: "spec/controllers/resource_center_controller_spec.rb"
Running suite to find failures... (7.39 seconds)
Starting bisect with 1 failing example and 5 non-failing examples.
Checking that failure(s) are order-dependent... failure appears to be order-dependent
Round 1: bisecting over non-failing examples 1-5 .. multiple culprits detected - splitting candidates (13.84 seconds)
Round 2: bisecting over non-failing examples 1-3 . ignoring examples 1-2 (6.95 seconds)
Round 3: bisecting over non-failing examples 4-5 . ignoring example 4 (6.75 seconds)
Bisect complete! Reduced necessary non-failing examples from 5 to 2 in 34.1 seconds.
The minimal reproduction command is:
rspec ./spec/controllers/resource_center_controller_spec.rb[1:1:1:1:2:1:1:1,1:1:1:1:2:2:1:1,1:1:1:1:2:2:1:2]
Edit: here is the factory for mediafile
FactoryBot.define do
# pi = PlatformInstance.select
factory :mediafile do
name { Faker::Simpsons.character }
platform_instance_uuid { PlatformInstance.first.uuid } # stick to platforminstance.first for now
platform_instance { PlatformInstance.first } # had tried to use a variable, but was
# not working
description { Faker::Simpsons.quote }
document { File.new("#{Rails.root}/spec/support/fixtures/mediafiles/document_01.pdf") }
image { File.new("#{Rails.root}/spec/support/fixtures/mediafiles/image_01.jpg") }
# review_with_mediafiles will create mediafile data after the review has been created
factory :mediafile_with_review do
after(:create) do |mediafile, evaluator|
create(:review, mediafile: mediafile)
end
end
end
end
and here is the factory for resource center
FactoryBot.define do
factory :resource_center do
title { Faker::Company.catch_phrase }
description { Faker::Lorem.paragraph(10) }
launch_at { Time.now }
expire_at { Time.now + 1.week }
platform_instance_uuid { PlatformInstance.first.uuid } # stick to PlatformInstance.first for now
platform_instance { PlatformInstance.first } # had tried to use a variable, but was
# not working
status { [:testing, :live].sample }
# review_with_mediafiles will create mediafile data after the review has been created
# this factory inherits everything from the factory it is nested under
factory :resource_center_with_mediafiles do
after(:create) do |resource_center, evaluator|
create(:mediafile, resource_centers: [resource_center])
end
end
end
end
The controller method itself is fairly simple
def index
#resource_centers = current_user.resource_centers.within_dates
end
current_user variable is assigned in the application controller which I don't think is super necessary to include here. The view is also fairly simple and can be seen below
-content_for :breadcrumbs do
=render 'layouts/shared/breadcrumbs', breadcrumbs: [link_to('Home', user_root_path), 'Resource Center']
-files_present = false
-#resource_centers.each do |resource_center|
-if resource_center.mediafiles.present?
-files_present = true
%h3.color-primary= resource_center.title.html_safe
=resource_center.description.html_safe
.space-above-2
-resource_center.mediafiles.sort.each do |mediafile|
=render 'resource_center/mediafile_item', resource_center: resource_center, mediafile: mediafile
-if !files_present
%h4 There are no files for your review at the moment.
Here is the partial rendered in the above view.
.index-list
.index-item.large-avatar
.item-avatar
=link_to resource_center_mediafile_view_path(resource_center, mediafile) do
= image_tag mediafile.image.url
.item-content
.item-header= mediafile.name
.item-attribute-list
%span.item-attribute
-if mediafile.duration.present?
%strong DURATION:
=pluralize(mediafile.duration, "minute")
-if mediafile.document.size.to_i > 0
%strong SIZE:
=number_to_human_size(mediafile.document.size)
.item-actions
-if resource_center.downloadable
=link_to 'Download', mediafile.download_url, class: 'mui-button default', target: '_blank'
=link_to 'View', resource_center_mediafile_view_path(resource_center, mediafile), class: 'mui-button'
Here is the spec_helper file:
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
RSpec.configure do |config|
config.before(:each) do
#only modify the request when testing controllers
if described_class <= ApplicationController
request.host = 'localhost:3000'
end
end
config.include Rails.application.routes.url_helpers
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
config.before(:all) do
DatabaseCleaner.start
end
config.after(:all) do
DatabaseCleaner.clean
end
config.shared_context_metadata_behavior = :apply_to_host_groups
# config.include Rails.application.routes.url_helpers
end
Please let me know if there is other information that would be helpful. I think this is something wrong with my test suite, particularly the before(:each) blocks, but my experimentation has not given me any insights.
Disclaimer: I did not read the whole code you posted, so I won't give you the answer what causes this flakiness (or flappines as you call it) but I'll give you a method to find it yourself. (fish vs. fishing rod thing kinda thing)
Using bisect is great, and since it says that the issue is order dependent it's fairly easy to continue.
You can now set a breakpoint in the failing it and investigate why the results are different than expected. Most probably there's some leftover junk in the DB left from some other spec.
When you pinpoint the reason for the failing spec, you can run command:
rspec --format doc \
./spec/controllers/resource_center_controller_spec.rb[1:1:1:1:2:1:1:1,1:1:1:1:2:2:1:1,1:1:1:1:2:2:1:2]
This will tell you in what order the tests are run (since [1:1:1:1:2:1:1:1,1:1:1:1:2:2:1:1,1:1:1:1:2:2:1:2] is not very human friendly)
and you can look for the spec that leaves the "state unclean" (mentioned DB junk, but could be something else)
When you pin-point the offender you can add some crude fix (like Model.destroy_all after it, to confirm that it's The Reason).
Please note that this is not the proper fix yet.
After you confirm that this is true - you're ready to search for a solution. This can be using DBCleaner for your specs, or fixing some cache code that is misbehaving or something completely different (feel free to ask another question when you have the answers)
One extra note: in many projects order of the specs will be randomized. In such case bisecting will fail unless you know the --seed under which the specs fail.

Rspec-rails overloading describe, breaking existing minitests

I'm using minitest 5.8.4 and rspec-rails 3.5.1. We have a current test suite that's using minitest, but I'm going to slowly migrate us to rspec.
I currently have a lot of tests that are structured like the following:
class UserTest < ActiveSupport::TestCase
describe "a_method" do
it "should return the results" do
assert_a_thing
end
end
end
As soon as I include rspec-rails in my Gemfile, it appears that the describe method is then globally overloaded/taken by RSpec, and when I run rake test it simply skips all of those tests.
Tests that are are in the structure of test 'foo' {it 'works' {}} aren't skipped.
How can I easily make it so that my new RSpec tests, and existing minitests using describe co-exist peacefully?
I think this is because of rspec's monkey patching. In your spec_helper disable monkey patching.
RSpec.configure do |c|
c.disable_monkey_patching!
end
You would then need to have the following in your specs
RSpec.describe "whatever" do
# any describe, scenario, it blocks here don't need the RSpec. prefix
end
Expanding on j-dexx's answer, once you've removed the RSpec version of describe you can add back in the minitest ones with another monkey patch. I've done something like that in this commit for the Chef minitest-handler-cookbook. Mine is base on how minitest/spec looks in 4.7.3, but I'm pretty sure it's similar in the newer minitest.
RSpec.configure { |c| c.disable_monkey_patching! }
[RSpec::Core::DSL.top_level, Module].each do |klass|
klass.class_exec do
# copied from minitest/spec
# https://github.com/seattlerb/minitest/blob/v5.14.4/lib/minitest/spec.rb#L75-L90
def describe desc, *additional_desc, &block # :doc:
stack = Minitest::Spec.describe_stack
name = [stack.last, desc, *additional_desc].compact.join("::")
sclas = stack.last || if Class === self && kind_of?(Minitest::Spec::DSL) then
self
else
Minitest::Spec.spec_type desc, *additional_desc
end
cls = sclas.create name, desc
stack.push cls
cls.class_eval(&block)
stack.pop
cls
end
end
end

How to remove desc messages from rspec when using thor?

Now I am using thor in a rails project.
I wrote these codes:
lib/tasks/my_task.rb
require 'thor'
module Tasks
class MyTask < Thor
desc 'My Batch', 'This is my awesome batch'
option :date
def execute(type)
# do_something
end
end
end
Tasks::MyTask.start(ARGV)
spec/lib/tasks/my_task_spec.rb
require 'spec_helper'
describe 'Test my task' do
context 'With date option' do
before do
#option = { date: '20150903' }
end
it 'Can insert to db' do
expect do
Tasks::MyTask.new.invoke(:execute, ['commit'], #option)
end.to change(ProductTable, :count).by(1)
end
end
end
The problem is when I run bundle exec rspec, it showed:
Run options: exclude {:heavy=>true}
..................................................................................................................................................................................................****************************...................................................
........................................................************.......................................................................................................................................................................................................******
Commands:
rspec help [COMMAND] # Describe available commands or one specific command
rspec My Batch # This is my awesome batch
.................................................................................................................................................................................................................................................................................
Why the desc messages been shown here? How to config to remove them?
Haven't tested it, but I'd imagine you could probably patch it out with something like:
module Thor
def puts(*args, &block)
# do nothing
end
end
EDIT: actually it looks like this might be handled a little differently here: https://github.com/erikhuda/thor/blob/master/lib/thor/shell/basic.rb
It looks like you could stub out Thor::Shell::Basic#stdout and #stderr

Capybara Acceptance Tests: undefined method `visit' for #<RSpec::Core::ExampleGroup

I'm in the process of upgrading an old krufty application to Rails 3.1. The company has been using RSpec and Capybara for acceptance tests. We've got some acceptance tests under spec/acceptance that are failing with the following message:
Failure/Error: get #url
NoMethodError:
undefined method `get' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0x007feb7c0abf58>
Here's an example of one of the tests (from the top of the file):
require_relative 'acceptance_helper'
feature 'Catalog' do
before do
Settings.use_catalog_navigation = true
end
context 'with a Vendor' do
before do
#vendor = create(:vendor, slug: 'abc')
#product = create(:product_with_variants, vendor: #vendor)
#non_vendor_product = create(:product_with_variants)
#invisible_product = create(:product_with_variants,
vendor: #vendor,
visible: false)
#non_available_product = create(:product_with_variants,
vendor: #vendor,
available: false)
#url = "/#{#vendor.slug}"
end
it 'sets #vendor' do # <- FIRST FAILING TEST
get #url
assigns(:vendor).should == #vendor
end
...
When consulting the oracle, I keep stumbling across issues mentioning the 'visit' method such as this: https://github.com/jnicklas/capybara/issues/814
I also keep coming across posts related to this article: http://alindeman.github.com/2012/11/11/rspec-rails-and-capybara-2.0-what-you-need-to-know.html
I'm not sure that I'm having the same issue. I can post my spec_helper.rb and acceptance_helper.rb if they will be of any use.
I guess it's worth noting that these specs passed before I updated rspec-rails and capybara.
My gut feeling is that perhaps rspec-rails is clobbering some of capybara's methods, or some of capybara's methods are simply no longer being loaded. Could that be the issue?
Try and add config.include Capybara::DSL in spec_helper.rb, inside the config block. like so:
RSpec.configure do |config|
config.include Capybara::DSL

Resources