Rspec which actually tests method with instance variable - ruby-on-rails

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

Related

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

How to stub a method that sets an object attribute in a Rails test?

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)

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)

How to pass Arguments and use those in (resque-status) Resque::JobWithStatus?

my resque worker class is:
require 'resque'
require 'resque/job_with_status'
class PatstatResqueWorker < Resque::JobWithStatus
#queue = :my_worker_q
def self.perform(query, label)
puts "query:"
puts options['query']
puts "label:"
puts options['label']
end
end
and my controller part, where I call this resque is...
class MyController < ApplicationController
def resque
job_id = PatstatResqueWorker.create(:query => #query, :label => "yes")
status = Resque::Plugins::Status::Hash.get(job_id)
end
end
and its not working :(
if i remove the parameter from resque function it says Wrong number of arguments (2 for 0) and if i add the parameter section back it says options not defined :(
Could you help?
The reason you're getting the "options not defined" error is that you haven't defined options in the method that uses it. Your self.perform method expects to receive two distinct arguments, query and label, but the code inside the method expects to have an options hash. You've got to choose one or the other.
Either do this:
def self.perform(query, label)
# use the parameters we've already defined
puts "query:"
puts query
puts "label:"
puts label
end
# call it like this
PatstatResqueWorker.create(#query, "yes")
Or else do this:
# change the method signature to match what you're doing
def self.perform(options)
puts "query:"
puts options['query']
puts "label:"
puts options['label']
end
# call it like this, with string keys
PatstatResqueWorker.create('query' => #query, 'label' => "yes")
Notice that with the hash version, I changed the call to use strings for the hash keys instead of symbols. You can use symbols if you want, but you'd have to change it in the body of the method as well (i.e. options[:query] instead of options['query']). You've just got to be consistent.

Is there a way to access method arguments in Ruby?

New to Ruby and ROR and loving it each day, so here is my question since I have not idea how to google it (and I have tried :) )
we have method
def foo(first_name, last_name, age, sex, is_plumber)
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{SOMETHING}"
end
So what I am looking for way to get all arguments passed to method, without listing each one. Since this is Ruby I assume there is a way :) if it was java I would just list them :)
Output would be:
Method has failed, here are all method arguments {"Mario", "Super", 40, true, true}
In Ruby 1.9.2 and later you can use the parameters method on a method to get the list of parameters for that method. This will return a list of pairs indicating the name of the parameter and whether it is required.
e.g.
If you do
def foo(x, y)
end
then
method(:foo).parameters # => [[:req, :x], [:req, :y]]
You can use the special variable __method__ to get the name of the current method. So within a method the names of its parameters can be obtained via
args = method(__method__).parameters.map { |arg| arg[1].to_s }
You could then display the name and value of each parameter with
logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ')
Note: since this answer was originally written, in current versions of Ruby eval can no longer be called with a symbol. To address this, an explicit to_s has been added when building the list of parameter names i.e. parameters.map { |arg| arg[1].to_s }
Since Ruby 2.1 you can use binding.local_variable_get to read value of any local variable, including method parameters (arguments). Thanks to that you can improve the accepted answer to avoid evil eval.
def foo(x, y)
method(__method__).parameters.map do |_, name|
binding.local_variable_get(name)
end
end
foo(1, 2) # => 1, 2
One way to handle this is:
def foo(*args)
first_name, last_name, age, sex, is_plumber = *args
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{args.inspect}"
end
This is an interesting question. Maybe using local_variables? But there must be a way other than using eval. I'm looking in Kernel doc
class Test
def method(first, last)
local_variables.each do |var|
puts eval var.to_s
end
end
end
Test.new().method("aaa", 1) # outputs "aaa", 1
If you need arguments as a Hash, and you don't want to pollute method's body with tricky extraction of parameters, use this:
def mymethod(firstarg, kw_arg1:, kw_arg2: :default)
args = MethodArguments.(binding) # All arguments are in `args` hash now
...
end
Just add this class to your project:
class MethodArguments
def self.call(ext_binding)
raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding)
method_name = ext_binding.eval("__method__")
ext_binding.receiver.method(method_name).parameters.map do |_, name|
[name, ext_binding.local_variable_get(name)]
end.to_h
end
end
This may be helpful...
def foo(x, y)
args(binding)
end
def args(callers_binding)
callers_name = caller[0][/`.*'/][1..-2]
parameters = method(callers_name).parameters
parameters.map { |_, arg_name|
callers_binding.local_variable_get(arg_name)
}
end
You can define a constant such as:
ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge"
And use it in your code like:
args = eval(ARGS_TO_HASH)
another_method_that_takes_the_same_arguments(**args)
If the function is inside some class then you can do something like this:
class Car
def drive(speed)
end
end
car = Car.new
method = car.method(:drive)
p method.parameters #=> [[:req, :speed]]
If you would change the method signature, you can do something like this:
def foo(*args)
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{args}"
end
Or:
def foo(opts={})
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{opts.values}"
end
In this case, interpolated args or opts.values will be an array, but you can join if on comma. Cheers
It seems like what this question is trying to accomplish could be done with a gem I just released, https://github.com/ericbeland/exception_details. It will list local variables and vlaues (and instance variables) from rescued exceptions. Might be worth a look...
Before I go further, you're passing too many arguments into foo. It looks like all of those arguments are attributes on a Model, correct? You should really be passing the object itself. End of speech.
You could use a "splat" argument. It shoves everything into an array. It would look like:
def foo(*bar)
...
log.error "Error with arguments #{bar.joins(', ')}"
end

Resources