how to use rspec to mock a class method inside a module - ruby

Im trying to test a create method that has a call to an external API but I'm having trouble mocking the external API request. Heres my setup and what I've tried so far:
class Update
def self.create(properties)
update = Update.new(properties)
begin
my_file = StoreClient::File.get(properties["id"])
update.filename = my_file.filename
rescue
update.filename = ""
end
update.save
end
end
context "Store request fails" do
it "sets a blank filename" do
store_double = double("StoreClient::File")
store_double.should_receive(:get).with(an_instance_of(Hash)).and_throw(:sad)
update = Update.create({ "id" => "222" })
update.filename.should eq ""
end
end
at the moment Im getting this failure
Failure/Error: store_double.should_receive(:get).with(an_instance_of(Hash)).and_throw(:sad)
(Double "StoreClient::File").get(#<RSpec::Mocks::ArgumentMatchers::InstanceOf:0x000001037a9208 #klass=Hash>)
expected: 1 time
received: 0 times
why is my double not working and how is best to mock the call to StoreClient::File.get, so that I can test the create method when it succeeds or fails?

The problem is that double("StoreClient::File") creates a double called "StoreClient::File", it does not actually substitute itself for the real StoreClient::File object.
In your case I don't think you actually need a double. You can stub the get method on the StoreClient::File object directly as follows:
context "Store request fails" do
it "sets a blank filename" do
StoreClient::File.should_receive(:get).with(an_instance_of(Hash)).and_throw(:sad)
update = Update.create({ "id" => "222" })
update.filename.should eq ""
end
end

Related

How do I use double and message chain in ruby rspec

I am working on Plain Ruby Project(Non Rails Environment). However, I am getting an error,
#<Double "pr"> received unexpected message :client with (no args)
This error is returned from the double object with label pr.
Here's my rspec, I have configured the Sinatra as a fake github server and its returning a JSON response.
I have verifed the result and its returning a JSON response.
RSpec.describe Humdrum::DefaultVerifications::CodeReviewsSignedOff do
describe '.Code Reviews Signed Off' do
let(:org){
'octocat'
}
let(:number){
123
}
before do
uri = URI('https://api.github.com/repos/octocat/hello-world/pulls/42/reviews')
result=JSON.load(Net::HTTP.get(uri))
github=double
allow(github)
.to receive_message_chain(:client,:pull_request_reviews)
.with(:org,:number)
.and_return (result)
end
it 'should check pull request review response' do
object=Humdrum::DefaultVerifications::CodeReviewsSignedOff.new
github=double("pr")
expert(object.pull_request_reviews_response(github)). to eq(1)
end
end
end
As you can see in the function pull_request_reviews_response, I want to stub the github.client.pull_request_reviews, hence, in the rspec for this file,
I wrote allow, message chain and from there it returns json response.
That json response will be proccessed inside the same function and return a integer response
module Humdrum
module DefaultVerifications
class CodeReviewsSignedOff
def pull_request_reviews_response(github)
#Counting total number of user who have approved the pull request
approveCount=0
github.client.pull_request_reviews("#{github.organization}/#{github.repository}", github.number).each do |review|
username = review[:user][:login]
state = review[:state]
if state == "APPROVED" and !##approvedUser.include?(username)
##approvedUser.add(username)
puts "Changes #{state} by #{username}"
approveCount += 1
end
end
return approveCount
end
What am I doing wrong?
What am I doing wrong?
You have defined github as a local variable, in two different places:
before
# ...
github = double # <----- HERE
allow(github)
.to receive_message_chain(:client, :pull_request_reviews)
.with(:org, :number)
.and_return(result)
end
it 'should check pull request review response' do
# ...
github = double("pr") # <-- AND ALSO HERE
expert(object.pull_request_reviews_response(github)).to eq(1)
end
So the object you send to the method doesn't have any stubs. Hence the error message.
There are various ways you could choose to structure this test (for instance, we could talk about how using double is generally a bad idea, and so is using receive_message_chain !... I'd opt to at least use instance_double, or potentially even just pass a real object here.).
But as a minimal change, here's a way you could define the github variable once, and reference the same object in the before block and the spec itself:
let(:github) { double("pr") } # <---- !!!
before
# ...
allow(github)
.to receive_message_chain(:client, :pull_request_reviews)
.with(:org, :number)
.and_return(result)
end
it 'should check pull request review response' do
# ...
expert(object.pull_request_reviews_response(github)).to eq(1)
end

How do I check that the value of a param is a number in a certain range

I am trying to write a test suite for a method that sends a POST request with a parameter 'target' that has to be between 0 and 10
My Ruby class:
class ClassName
before_action :must_have_valid_target
def create
target = params[:target]
. . .
end
def must_have_valid_target
return if params.key?(:target)
error_response(422, 'error message')
end
end
My Rspec
it 'cannot create request with negative target' do
post(:create, {target: -1})
assert_response(422) # actual result is: Expected 422, Actual 200
end
I tried:
def must_have_valid_target
valid = params[:target].between?(0,10)
end
but this does not work. How do I check that the symbol has a value between the range so I can give the correct response afterwards?
This is not homework, I am trying to add additional tests to the codebase at my workplace but I am still very new to RSpec and Ruby.
params[:target] is a string, cast to integer prior to the comparison,
def must_have_valid_target
params[:target].present? && params[:target].to_i.between?(0,10)
end

Using variable declared in one method to open webpage in another method

I am working on a CLI Project and trying to open up a web page by using url variable declared in another method.
def self.open_deal_page(input)
index = input.to_i - 1
#deals = PopularDeals::NewDeals.new_deals
#deals.each do |info|
d = info[index]
#product_url = "#{d.url}"
end
#product_url.to_s
puts "They got me!"
end
def self.deal_page(product_url)
#self.open_deal_page(input)
deal = {}
html = Nokogiri::HTML(open(#product_url))
doc = Nokogiri::HTML(html)
deal[:name] = doc.css(".dealTitle h1").text.strip
deal[:discription] = doc.css(".textDescription").text.strip
deal[:purchase] = doc.css("div a.button").attribute("href")
deal
#binding.pry
end
but I am receiving this error.
`open': no implicit conversion of nil into String (TypeError)
any possible solution? Thank you so much in advance.
Try returning your #product_url within your open_deal_page method, because now you're returning puts "They got me!", and also note that your product_url is being created inside your each block, so, it won't be accessible then, try creating it before as an empty string and then you can return it.
def open_deal_page(input)
...
# Create the variable
product_url = ''
# Assign it the value
deals.each do |info|
product_url = "#{info[index].url}"
end
# And return it
product_url
end
In your deal_page method tell to Nokogiri to open the product_url that you're passing as argument.
def deal_page(product_url)
...
html = Nokogiri::HTML(open(product_url))
...
end

Assignment method created using define_singleton_method returns the wrong value

Background
The Entity class is a base class that gets inherited by several subclasses that holds entities received over a REST API. The entity classes are immutable and should return a new instance of themselves whenever a change is attempted.
The Entity class has an .update() method that takes a hash of values to update, if the changes aren't really changes it returns itself and if there are real changes it returns a new instance of itself with the changes effected before instantiation.
To be user friendly Entity also allows for direct assignment to properties (so that if a subclass of Entity has a name attribute you can do instance.name = 'New Name') that also returns a new instance of the class. This is implemented in terms of update using dynamic methods that are created when the class is instantiated.
And they are the problem.
Problem
The code in the Entity class looks, in part, like this (for a complete code listing and tests check out the Github repo: https://github.com/my-codeworks/fortnox-api.git):
require "virtus"
require "ice_nine"
class Entity
extend Forwardable
include Virtus.model
def initialize( hash = {} )
super
create_attribute_setter_methods
IceNine.deep_freeze( self )
end
def update( hash )
attributes = self.to_hash.merge( hash )
return self if attributes == self.to_hash
self.class.new( attributes )
end
private
def create_attribute_setter_methods
attribute_set.each do |attribute|
name = attribute.options[ :name ]
create_attribute_setter_method( name )
end
end
def create_attribute_setter_method( name )
self.define_singleton_method "#{name}=" do | value |
self.update( name => value )
end
end
end
Doing this:
instance.update( name: 'New Name' )
and this:
instance.name = 'New Name'
Should be the same, literally since one is implemented in terms of the other.
While .update() works perfectly the .attr=() methods return the value you assign.
So in the above example .update() returns a new instance of the Entity subclass but .attr=() returns 'New Name' ...
I have tries capturing the output inside the .attr=() method and log it before returning so that I have this:
self.define_singleton_method "#{name}=" do | value |
p "Called as :#{name}=, redirecting to update( #{name}: #{value} )"
r = self.update( name => value )
p "Got #{r} back from update"
return r
end
And the log lines say:
"Called as :name=, redirecting to update( name: 'New Name' )"
"Got #<TestEntity:0x007ffedbd0ad18> back from update"
But all I get is the string 'New Name'...
My forehead is bloody and no posts I find show anything close to this. I bet I'm doing something wrong but I can't find it.
Getting dirty
The Github repo has tests in rspec that you can run, the failing ones are focused right now and some extra logging is in the Entity class to capture the different internal steps.
Comments, links and/or pull requests are welcome.
Turns out that the = methods always return the value being assigned.
o = Struct.new(:key).new(1)
o.define_singleton_method("something") { #something }
o.define_singleton_method("something=") do |v|
#something = v
return 6
end
As you can see, I've 'fixed' the return value to 6 each time something= is called. Let's see if it works:
o.something = 1 #=> outputs 1, not 6
o.something #=> outputs 1, so the method did indeed run
Conclusion? My guess is that an = method will return the value that you are assigning through it. And IMO it's better this way; one reason would be to ensure proper functioning of assignment chains:
new_val = o.something = some_val

how do you mock dependent methods using rspec

I'm trying to write a custom parser for my cucumber results. In doing so, I want to write rspec tests around it. What I currently have is as follows:
describe 'determine_test_results' do
it 'returns a scenario name as the key of the scenario results, with the scenario_line attached' do
pcr = ParseCucumberJsonReport.new
expected_results = {"I can login successfully"=>{"status"=>"passed", "scenario_line"=>4}}
cucumber_results = JSON.parse(IO.read('example_json_reports/json_passing.json'))
pcr.determine_test_results(cucumber_results[0]).should == expected_results
end
end
The problem is, determine_test_results has a sub method called determine_step_results, which means this is really an integration test between the 2 methods and not a unit test for determine_test_results.
How would I mock out the "response" from determine_step_results?
Assume determine_step_results returns {"status"=>"passed", "scenario_line"=>4}
what I have tried:
pcr.stub(:determine_step_results).and_return({"status"=>"passed", "scenario_line"=>6})
and
allow(pcr).to receive(:determine_step_results).and_return({"status"=>"passed", "scenario_line"=>6})
You could utilize stubs for what you're trying to accomplish. Project: RSpec Mocks 2.3 would be good reading regarding this particular case. I have added some code below as a suggestion.
describe 'determine_test_results' do
it 'returns a scenario name as the key of the scenario results, with the scenario_line attached' do
pcr = ParseCucumberJsonReport.new
expected_results = {"I can login successfully"=>{"status"=>"passed", "scenario_line"=>4}}
# calls on pcr will return expected results every time determine_step_results is called in any method on your pcr object.
pcr.stub!(:determine_step_results).and_return(expected_results)
cucumber_results = JSON.parse(IO.read('example_json_reports/json_passing.json'))
pcr.determine_test_results(cucumber_results[0]).should == expected_results
end
end
If all what determine_test_results does is call determine_step_results, you should not really test it, since it is trivial...
If you do decide to test it, all you need to test is that it calls the delegate function, and returns whatever is passed to it:
describe ParseCucumberJsonReport do
describe '#determine_test_results' do
it 'calls determine_step_results' do
result = double(:result)
input = double(:input)
expect(subject).to receive(:determine_step_results).with(input).and_return(result)
subject.determine_test_results(input).should == result
end
end
end
If it is doing anything more (like adding the result to a larger hash) you can describe it too:
describe ParseCucumberJsonReport do
describe '#determine_test_results' do
it 'calls determine_step_results' do
result = double(:result)
input = double(:input)
expect(subject).to receive(:determine_step_results).with(input).and_return(result)
expect(subject.larger_hash).to receive(:merge).with(result)
subject.determine_test_results(input).should == result
end
end
end

Resources