Rails model test - should_receive method not working - ruby-on-rails

I'm writing a pretty straightforward method. Whenever a referer has referred 5 people to become new users, I want them to get a refund. This means that when a new user is created, there's a method check_referer that checks to see if the person who referred them (if this person exists) should get a refund because they've now referred 5 people in total.
In the test logs, based on the puts statement, I can tell that the code is working and the refund_membership_fees_paid method is indeed being called once. But the test keeps failing with:
Failure/Error: #referer.should_receive(:refund_membership_fees_paid).exactly(1).times
(#<User:0x007fbf46bf1c58>).refund_membership_fees_paid(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
Test code:
describe User, "Test refund_membership_fees_paid method is called" do
before do
#referer = User.new()
#referer.save(validate:false)
RefererRefundAlert.stub_chain(:new, :async, :perform)
end
it "at 5 users" do
5.times do |index|
u = User.new(referred_by: #referer.id)
u.save(validate:false)
end
#referer.should_receive(:refund_membership_fees_paid).exactly(1).times
end
end
Model code:
def check_referer
if self.referred_by.present? && User.where(referred_by: self.referred_by).count == 5
User.find(self.referred_by).refund_membership_fees_paid
end
end
def refund_membership_fees_paid
puts "refund_membership_fees_paid method"
RefererRefundAlert.new.async.perform(self.id)
end

User.find does not return the same object as #referer; it will return a different instance of User that represents the same user in the database.
Instead of checking whether refund_membership_fees_paid is called, you can verify that the correct user ID is getting passed intoRefererRefundAlert.new.async.perform.
Also, as others mentioned, you should set your expectation before running the tested methods.
RefererRefundAlert.new.async.should_receive(:perform)
.with(#referer.id).exactly(1).times
5.times do |index|
u = User.new(referred_by: #referer.id)
u.save(validate:false)
end

The use of should_receive is to set the expectation for the following action.
For example, if your account.close action is supposed to log the closure, the test would be...
logger.should_receive(:account_closed)
account.close
So your example should be restructured to put the test first...
#referer.should_receive(:refund_membership_fees_paid).exactly(1).times
5.times {User.new(referred_by: #referer.id).save(validate: false)}

Related

When using factory_bot (formerly factory_girl) with RSpec to test in Rails, undefined method "first"

I have an RSpec test that uses factory_bot to create instances. The test passes except when the first method is used in a view.
This is the code being tested:
def order_confirm_email(id, items, order, address, coupon)
#user = User.find(id)
#items = items
#order = order
#address = address
if coupon == nil
#coupon = ''
else
#coupon = coupon.discount
end
mail(to: #user.email, subject: 'Order completed')
end
This is the test:
it 'sends an email upon checkout process completion' do
user = create(:user)
items = create(:base_item)
order = create(:base_item)
address = create(:address)
coupon = create(:coupon)
expect(orderConfirmationMail.subject).to eq('Order completed')
end
So far so good. But when one of the views attempts to access the first instance, as below:
<h1>Order id - <%= #order.first.id %></h1>
Then I receive the following error:
Failures:
1) UserMailer user_emails sends an email upon checkout process completion
Failure/Error: <!-- <h1>Order id - <%= #order.first.id %></h1> -->
ActionView::Template::Error:
undefined method `first' for 3:Fixnum
According to my understanding, the instances should be persisting since I use create instead of build. But apparently that is not happening. Changing the view is not an option except as a last resort. How do I resolve this?
UPDATE 1:
This is the order_items.rb Factory file:
FactoryBot.define do
factory :base_item, class: OrderItem do
item_name_en "Sample Item"
item_link "http://www.foo.com"
qty 5
available_qty 100
item_price 10
seller_name "foo"
status "foo"
foo_item_id "123456"
foo_id "654321"
end
factory :order_item1, parent: :base_item do
foo_item_id "123456"
foo_seller_id "654321"
association :order
end
factory :order_item2, parent: :base_item do
foo_item_id "123457"
foo_seller_id "654321"
end
factory :order_item3, parent: :base_item do
foo_item_id "123458"
foo_seller_id "654322"
end
end
UPDATE 2:
I am encountering a similar issue when writing another test. I receive the following error:
Failures:
1) UserMailer user_emails sends an email upon abandoned cart
Failure/Error: <% #items.each do |item| %>
ActionView::Template::Error:
undefined method `each' for 1:Fixnum
This seems to indicate that it is not an issue with the first method per se, but rather with the interpolated Ruby in the views throwing an error when I run RSpec. This generalizes the problem and hopefully makes it more easily solvable.
that is not an issue, as earlier in the spec I include this line that I know is working correctly because I use the same format in another test successfully
let(:orderConfirmationMail) { UserMailer.order_confirm_email(1,2,3,4,nil) }
There it is. If you included this line from the start, your question would've been answered in two seconds.
Considering the signature of order_confirm_email
def order_confirm_email(id, items, order, address, coupon)
Why do you think order is set to 3? Because you pass it this way!
Mystery solved.
sorry, I don't have any idea. I would give a look to your OrderItem model, also I don't get why you do #order.first because that is just 1 object. Could make sense to me #orders.first but not #order.first. When things don't make sense in rails it is hard to build applications. also order and item should be two different models, we use order_items for building joins between two different models
An order can have many or just one item, you decide the relationship.
with rspec you can debug and also you can test your factories in the rails c test environment. There you can check what is the result of create(:base_item) or of FactoryGirl.create(:base_item)
I believe that should be an object, so I don't understand why it is saying undefined method 'first' for 3:Fixnum
Maybe the you factory is not what we expect
but you can understand this by debugging and testing in the console your factory

Why will this test not run with sucker punch gem?

The purpose of this code is to send an email to a user with an array of products whose discount percentages have reached a given threshold. The products are returned by:
user.notifications
which returns an array of 2 element arrays with the following format:
[[product, notification]]
A notification is an object composed of a discount percentage and a product_id.
send_notification?
checks to see if a user has been sent a notification in the last 7 days and returns a boolean (true if they have not received an email in the last week and false if they have for the product being passed in.)
I have the following job and accompanying test:
class ProductNotificationEmailJob
include SuckerPunch::Job
def perform(user)
user_notifications = user.notifications || []
products = []
notifications = []
user_notifications.each do |notification|
if notification[1].send_notification?
products << notification[0]
notifications << notification[1]
end
end
NotificationMailer.notification_email(user, products).deliver
notifications.each do |notification|
notification.update(notification_date: Time.now)
end
end
end
test:
require 'rails_helper'
describe ProductNotificationEmailJob do
it 'performs' do
notification = ObjectCreation.create_notification
expect(notification.notification_date).to be_nil
user = notification.user
stub = double("Object")
expect(NotificationMailer).to receive(:notification_email).with(user, [notification.my_product.product]).and_return(stub)
expect(stub).to receive(:deliver)
ProductNotificationEmailJob.new.perform(user)
expect(MyProductsNotification.last.notification_date).to_not be_nil
end
end
When I take out the line:
include SuckerPunch::Job
the test passes fine but I cannot get it to pass with that line in though. For some reason with the include SuckerPunch::Job line it seems as though the object creation method does not work and returns nil for all values. I apologize in advance if I didn't give enough detail but I didn't want to post too much code. Leave a comment and I will include any details requested. Thank you for your time I really appreciate it!
After looking at the problem with fresh eyes I realized I was violating encapsulation rules by even trying to do all of that stuff in the ProductNotificationEmailJob class. I extracted into another class and everything runs fine and is perfectly testable.

How to deal with stubbing new method with potential Nil class in Ruby and rspec

I have a situation where I stub the .new method of a class, but that makes it to return nil objects, and later those objects are needed, and I am not sure how to deal with it. Here is my rspec code:
describe ShopWorker do
describe '#perform' do
let(:worker) { ShopWorker.new }
it 'creates a new instance of Shopper' do
user = FactoryGirl.create(:user)
expect(Shopper).to receive(:new).with(user)
worker.perform(user.id)
end
end
end
And here is my Worker code:
class ShopWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
shopper = Shopper.new(user)
shopper.start # This fails because Shopper.new returns NIL
end
end
So, since I am stubbing the new method with expect(Shopper).to receive(:new).with(user), then when in the worker it does shopper.start, that is nil, and therefore it breaks. How should I solve this? Ideally, I would like to test that a new instance of Shopper is done and also that the method start is called for that instance.
There are a couple of things you can do:
Expect to receive :new but provide a return value (possibly a mock), using and_return(). The problem is that to receive has an implicit and_return(nil) unless you provide a return value explicitly.
Don't stub :new, let it do its job and expect :start on any Shopper instance: expect_any_instance_of(Shopper).to receive(:start).
Ask yourself what value this test provides. The test knows a lot about the implementation, to a point where you always have to change both. What is the impact of shopper.start? Can you assert anything about the actual business value?

Weird RSpec behaviour

I am experiencing something weird when trying to test a model through rspec. I have the code
it "has might that varies by atmost 30" do
instance.init(100)
expect(instance.members.count).to eq(1)
instance.members.each do |member|
puts "#{member.unit.name}"
end
expect(instance.members.count).to eq(1)
end
Whenenver I run the above test, both expect methods pass, signaling that there is only a single "member" record associated with the instance, but when I print out the name of each member, it prints a name twice, saying that there is two different members?
EDIT: Also, the method works correctly on the development server, but only have this issue during tests?
EDIT: method in question
def init(might)
transaction do
self.group.delegations.each do |delegation|
unit = delegation.unit
amount = (might / unit.might) * delegation.fraction
amount = amount.round
unless amount < 1
self.members.create(unit: unit, amount: amount)
end
end
end
end

Rails RR UnitTesting: Problems with mocking an ActiveRecord model method

Consider the following class and methods: (This class is obviously much more complete, but for the sake of this thread...):
class Order < ActiveRecord::Base
def check
if (self.user.phone == "55555555") do
self.a_certain_method
return
end
end
def a_certain_method
# Real implementation goes here
end
end
And the following Unit Test:
describe :do_route do
it "should call a_certain_method if user phone number matches 55555555" do
# Create a user
user = Factory(:user)
# Set hard-coded phone number
user.phone = "55555555"
user.save!
# Create an order made by the ordering user
order = Factory(:order, :ordering_user => user)
# Set expectation for a "a_certain_method" call
mock(order).a_certain_method
# Call the tested method
order.check
end
end
From some reason, the above test produces an RR::Errors::TimesCalledError error, which claims that a_certain_method was called 0 times instead of 1... I've been searching around the web for a solution with no luck.
I've tried building a similiar test on a non-activerecord class, and the test produces no errors.
I've used the debugger to check that it does reach the self.a_certain_method line, and also tried using the following instead of mock(order).a_certain_method:
any_instance_of(Order) do |o|
mock(o).a_certain_method
end
Does anyone have any idea how to solve this issue since i'm kind of desperate...
I figured out what the problem was, it failed since the number was already in the database. so it failed to save the hard coded user.phone change.
Thanks for the help though :)

Resources