Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

have_enqueued_mail raising undefined method to_sym for Hash #2575

Closed
jasonkarns opened this issue Feb 21, 2022 · 12 comments
Closed

have_enqueued_mail raising undefined method to_sym for Hash #2575

jasonkarns opened this issue Feb 21, 2022 · 12 comments

Comments

@jasonkarns
Copy link
Contributor

jasonkarns commented Feb 21, 2022

What Ruby, Rails and RSpec versions are you using?

ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [x86_64-darwin20]
Rails 6.1.4.6
RSpec 3.10
  - rspec-core 3.10.2
  - rspec-expectations 3.10.2
  - rspec-mocks 3.10.3
  - rspec-rails 5.0.3
  - rspec-support 3.10.3

Issue does not manifest on rspec-rails 5.0.2

Observed behaviour

With the following spec:

       expect { subject.send_coach_lead_notification }
         .to have_enqueued_mail(CoachingInquiryMailer, :coach_notification_bookings_promo)

We get:

     NoMethodError:
       undefined method `to_sym' for {}:Hash
       Did you mean?  to_s
                      to_set
Full backtrace
Run options: include {:locations=>{"./spec/services/coaching_inquiry_notifier_spec.rb"=>[111]}}

Randomized with seed 31320

CoachingInquiryNotifier
  when lead is from coach without offerings
    sends mailer with bookings promo (FAILED - 1)

Failures:

  1) CoachingInquiryNotifier when lead is from coach without offerings sends mailer with bookings promo
     Failure/Error:
       expect { subject.send_coach_lead_notification }
         .to have_enqueued_mail(CoachingInquiryMailer, :coach_notification_bookings_promo)

     NoMethodError:
       undefined method `to_sym' for {}:Hash
       Did you mean?  to_s
                      to_set
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/have_enqueued_mail.rb:148:in `block in deserialize_arguments'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/have_enqueued_mail.rb:148:in `each'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/have_enqueued_mail.rb:148:in `each_with_object'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/have_enqueued_mail.rb:148:in `deserialize_arguments'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/active_job.rb:148:in `arguments_match?'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/have_enqueued_mail.rb:89:in `arguments_match?'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/active_job.rb:104:in `block in check'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/active_job.rb:103:in `each'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/active_job.rb:103:in `partition'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/active_job.rb:103:in `check'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/active_job.rb:237:in `matches?'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/matchers/have_enqueued_mail.rb:40:in `matches?'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-expectations-3.10.2/lib/rspec/expectations/handler.rb:51:in `block in handle_matcher'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-expectations-3.10.2/lib/rspec/expectations/handler.rb:27:in `with_matcher'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-expectations-3.10.2/lib/rspec/expectations/handler.rb:48:in `handle_matcher'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-expectations-3.10.2/lib/rspec/expectations/expectation_target.rb:65:in `to'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-expectations-3.10.2/lib/rspec/expectations/expectation_target.rb:101:in `to'
     # ./spec/services/coaching_inquiry_notifier_spec.rb:112:in `block (3 levels) in '
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:262:in `instance_exec'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:262:in `block in run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:508:in `block in with_around_and_singleton_context_hooks'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:465:in `block in with_around_example_hooks'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/hooks.rb:486:in `block in run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:350:in `call'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-rails-5.0.3/lib/rspec/rails/adapters.rb:75:in `block (2 levels) in '
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:455:in `instance_exec'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:455:in `instance_exec'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/hooks.rb:390:in `execute_with'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:350:in `call'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/hooks.rb:486:in `run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:465:in `with_around_example_hooks'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:508:in `with_around_and_singleton_context_hooks'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example.rb:259:in `run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example_group.rb:644:in `block in run_examples'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example_group.rb:640:in `map'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example_group.rb:640:in `run_examples'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example_group.rb:606:in `run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example_group.rb:607:in `block in run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example_group.rb:607:in `map'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/example_group.rb:607:in `run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/runner.rb:121:in `map'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/configuration.rb:2067:in `with_suite_hooks'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/runner.rb:116:in `block in run_specs'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/reporter.rb:74:in `report'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/runner.rb:115:in `run_specs'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/runner.rb:89:in `run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/runner.rb:71:in `run'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/lib/rspec/core/runner.rb:45:in `invoke'
     # /usr/local/var/rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.2/exe/rspec:4:in `'
     # ./bin/rspec:29:in `load'
     # ./bin/rspec:29:in `'

Finished in 0.72436 seconds (files took 2.59 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/services/coaching_inquiry_notifier_spec.rb:111 # CoachingInquiryNotifier when lead is from coach without offerings sends mailer with bookings promo

Randomized with seed 31320

Expected behaviour

This was a passing test before upgrading to rspec-rails 5.0.3

Can you provide an example app?

https://github.com/jasonkarns/fluffy-octo-memory

I presume this is the result of #2566 (and related to #2570 ).

@pirj
Copy link
Member

pirj commented Feb 21, 2022

Thanks for reporting.
I wonder how this very basic case fails. It is pretty much covered by this feature test, and we run Ruby 2.7.5p203 with Rails 6.1.4.6, e.g. fresh passing build.

It would be very helpful if you could provide a reproducible example.

@jasonkarns
Copy link
Contributor Author

@pirj pushed an rspec rails repro repo (say that again!) https://github.com/jasonkarns/fluffy-octo-memory

single model without anything special; single mailer. rspec from repo root will reproduce

@jasonkarns jasonkarns changed the title have_enqueued_mail raising error: undefined method `to_sym' for {}:Hash have_enqueued_mail raising undefined method `to_sym' for Hash Feb 21, 2022
@jasonkarns jasonkarns changed the title have_enqueued_mail raising undefined method `to_sym' for Hash have_enqueued_mail raising undefined method to_sym for Hash Feb 21, 2022
@jasonkarns
Copy link
Contributor Author

jasonkarns commented Feb 21, 2022

I just installed ruby 2.7.5 and still reproduced. (So you shouldn't need to downgrade to 2.7.3 if you don't wish to)

ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-darwin21]

@pirj
Copy link
Member

pirj commented Feb 22, 2022

There's definitely a bug in this line:

keywords.each_with_object({}) { |new_hash, keyword| puts new_hash, keyword; new_hash[keyword.to_sym] = hash[keyword] }

Block arguments should be the other way around, |keyword, new_hash|, and that fixes your case.

It's still surprising that this bug has gotten through all of our lines of defence, and how to write a spec to prevent this from re-appearing. Would you like to hack on it?

@JonRowe
Copy link
Member

JonRowe commented Feb 22, 2022

Its the result of a munged rubocop fix, it was initially a reduce which does have the args the other way round, so this was working but then broken to comply with the rubocop rule to not use reduce in these scenarios.

Spec wise we miss this because we don't test with an active record object, if you change the repro spec to use a string it doesn't encounter the bug.

I've tried to work up a repo with a fake global id job in our suite which isn't enough, and an active record one but it complains about it not being serialisable

@jasonkarns
Copy link
Contributor Author

I was able to repro with a non-AR class by ensuring the job's param matched GlobalID::Identification:

jasonkarns/fluffy-octo-memory@6dc4eab

class M
   include GlobalID::Identification
   def id
     1
   end
 end

ReproMailer.with(inquiry: M.new).message.deliver_later

@pirj
Copy link
Member

pirj commented Feb 22, 2022

Nice! Would you like to send a PR with an added spec for this?

@JonRowe
Copy link
Member

JonRowe commented Feb 22, 2022

I had a more complex example, but that actually works better, does not reproduce the issue within our test suite though

@pirj
Copy link
Member

pirj commented Feb 24, 2022 via email

@jasonkarns
Copy link
Contributor Author

Yep, apologies. I deleted my comment as soon as I realized it was due to old sibling repos.

@JonRowe
Copy link
Member

JonRowe commented Mar 7, 2022

The key to reproducing this was the fact it needed to be tested using a unified mailer which is a later Rails default than our base level

@JonRowe
Copy link
Member

JonRowe commented Mar 7, 2022

Closed in #2578 and released as 5.1.1

@JonRowe JonRowe closed this as completed Mar 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants