How to stub a method that sets an object attribute in a Rails test? - ruby-on-rails

I'm building a Rails spec test that has a Struct called temp_coverage, like this:
temp_coverage = Struct.new(:paydays) do
def calculate_costs
50
end
end
And in my spec, I call a method using the temp_coverage, but I'm getting an error since the code I am testing is doing the following:
temp_coverage.req_subscriber_election_amount = subscriber_election_amount
And I am getting an error:
NoMethodError: undefined method `req_subscriber_election_amount=' for < struct paydays=12 >
How can I stub out the setting of an attribute on a struct in my spec?

Is something like this you're looking for?
temp_coverage = double('temp_coverage', paydays: nil)
allow(temp_coverage).to receive(:calculate_costs).and_return(50)
allow(temp_coverage).to receive(:req_subscriber_election_amount=) do |argument|
temp_coverage.instance_variable_set(:#req_subscriber_election_amount, argument)
end
# Example:
temp_coverage.req_subscriber_election_amount = 123
puts temp_coverage.instance_variable_get(:#req_subscriber_election_amount)
# => 123
puts temp_coverage.paydays
# => nil
puts temp_coverage.calculate_costs
# => 50

I found a way to do this by using a named Struct. So once I named my Struct:
temp_coverage = Struct.new('CoverageClass', :paydays) do
def calculate_costs
50
end
end
I could then do the following:
Struct::CoverageClass.any_instance.stubs(:req_subscriber_election_amount).returns(25)

Related

Rspec which actually tests method with instance variable

I have a service method as
class Service
def maximum_match
max = #hash.values.max
Hash[#hash.select { |_k, v| v == max }]
end
end
My test is like
context 'Finding tags count' do
it 'counts tags and returns maximum match' do
service = Service.new
expect(service.maximum_match).to eq some_result
end
end
How can I pass any values #hash to run my test?
Error is NoMethodError:undefined method 'values' for nil:NilClass
Ninja, you can use service.instance_variable_set(#hash, your_value) right above the expect line
source

Why do I get "uninitialized constant" when creating custom hash class?

In Rails, I'm trying to create a custom hash class, So I created a file in lib/core_ext/hash_with_variants.rb with this content
class VariantsHash < Hash
def []=(string, value)
all_variants(string).each do |variant|
super(variant, value)
end
end
def [](string)
all_variants(string).detect do |variant|
super(variant)
end
end
private
def all_variants(string)
downcase_string = string.downcase
string_length = string.length
variants = [downcase_string]
string_length.times do |i|
variants << downcase_string[0, i] + downcase_string[i + 1 , string_length]
end
variants
end
end
but when I try and initialize a new object using
VariantsHash.new
I get the error
NameError: uninitialized constant VariantsHash
What else do I need to be doing to get this right?
You need to require the file in your /config/application.rb.

undefined method `set' for nil:NilClass in Rails even though similar code works in irb

The following code works fine in IRB (Interactive Ruby Shell):
require 'prometheus/client'
prometheus = Prometheus::Client.registry
begin
#requests = prometheus.gauge(:demo, 'Random number selected for this users turn.')
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
end
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
puts 'output: ' + test.to_s
2.4.0 :018 > load 'test.rb'
output: 123.0
=> true
2.4.0 :019 >
However, when I put the same code into my Ruby on Rails controller, the second time the user uses the application, the following error is returned:
undefined method `set' for nil:NilClass
Can someone tell me when I'm doing wrong? Thank you.
require 'prometheus/client'
class RandomnumbersController < ApplicationController
def index
#randomnumbers = Randomnumber.order('number DESC').limit(8)
#counter = 0
end
def show
#randomnumber = Randomnumber.find(params[:id])
end
def new
end
def create
#randomnumber = Randomnumber.new(randomnumber_params)
prometheus = Prometheus::Client.registry
begin
#requests = prometheus.gauge(:demo, 'Random number selected for this users turn.')
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
end
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
#randomnumber.save
redirect_to #randomnumber
end
private
def randomnumber_params
params.require(:randomnumber).permit(:name, :number)
end
end
Because there is no #requests for :demo argument.
When ORM cannot find any info in db it returns nil (NilClass)
and You're trying to do:
#requests.set({name: "test"}, 123)
it's interpreted like:
nil.set({name: "test"}, 123)
why it's causes this issue in second time?
cuz Your code changes #requests name attribute to be test and seems like :demo is not test or maybe in another part of Your app You're replacing/deleting data in database that makes: #requests = prometheus.gauge(:demo, 'Random number selected for this users turn.') to return nil
Solution:
in code level add this fixes to avoid such unpredictable situations (check for nil) :
unless #requests.nil?
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
end

RSpec: Mock not working for instance method and multiple calls

I'm trying to mock PriceInspector#get_latest_price below to test OderForm. There are two orders passed in, hence, I need to return two different values when mocking PriceInspector#get_latest_price. It all works fine with the Supplier model (ActiveRecord) but I can't run a mock on the PriceInspector class:
# inside the test / example
expect(Supplier).to receive(:find).and_return(supplier_1) # first call, works
expect(PriceInspector).to receive(:get_latest_price).and_return(price_item_1_supplier_1) # returns nil
expect(Supplier).to receive(:find).and_return(supplier_2) # second call, works
expect(PriceInspector).to receive(:get_latest_price).and_return(price_item_2_supplier_1) # returns nil
class OrderForm
include ActiveModel::Model
def initialize(purchaser)
#purchaser = purchaser
end
def submit(orders)
orders.each do |supplier_id, order_items|
#supplier = Organization.find(supplier_id.to_i)
#order_item = OrderItem.save(
price_unit_price: PriceInspector.new(#purchaser).get_latest_price.price_unit_price
)
[...]
end
end
end
class PriceInspector
def initialize(purchaser)
#purchaser = purchaser
end
def get_latest_price
[...]
end
end
Edit
Here's the updated test code based on Bogieman's answer:
before(:each) do
expect(Organization).to receive(:find).and_return(supplier_1, supplier_2)
price_inspector = PriceInspector.new(purchaser, item_1)
PriceInspector.stub(:new).and_return price_inspector
expect(price_inspector).to receive(:get_latest_price).and_return(price_item_1_supplier_1)
expect(price_inspector).to receive(:get_latest_price).and_return(price_item_2_supplier_2)
end
it "saves correct price_unit_price for first OrderItem", :focus do
order_form.submit(params)
expect(OrderItem.first.price_unit_price).to be_within(0.01).of(price_item_1_supplier_1.price_unit_price)
end
I think this should fix the instance method problem and allow you to check for the two different returns (provided you pass in the purchaser or a double) :
price_inspector = PriceInspector.new(purchaser)
PriceInspector.stub(:new).and_return price_inspector
expect(price_inspector).to receive(:get_latest_price).and_return(price_item_1_supplier_1)
expect(price_inspector).to receive(:get_latest_price).and_return(price_item_2_supplier_1)

Extending hash constant in another file

I've got a gem where in one of classes is sth similiar:
class Test
TESTING = {
:sth1 => 'foo',
:sth2 => 'bar'
}
# p Test.new.show
# should print 'cat'
def show
p TESTING[:sth3]
end
end
I extended in other file
# in other file
class Test
TESTING = {
:sth3 => 'cat'
}
end
But i need to use :sth3 in first file, as the first part of code stands.
Thx in advance.
You didn't extend it, you replaced the hash with a new one. Here's how to fix it:
# in the other file
Test::TESTING[:sth3] = 'cat'
I recommend using methods with lazy initialization, so that you can arrange the assignments in any order:
class Test
def self.testing
#testing ||= {}
end
testing[:sth1] = 'foo'
testing[:sth2] = 'bar'
end
# in the other file
Test.testing[:sth3] = 'cat'

Resources