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
Related
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
I can't find a basic explanation anywhere about how I can test, with Rack::Test, that a Ruby/Sinatra post method successfully saves data to a YAML store/file. (This explains testing get, which I can do(!), but not post; other mentions of testing post methods with rack/test seem irrelevant.) For self-study, I'm building a "to do" app in Ruby/Sinatra and I'm trying to use TDD everything and unit test like a good little boy. A requirement I have is: When a user posts a new task, it is saved in the YML store.
I was thinking of testing this either by seeing if a "Task saved" was shown in the response to the user (which of course isn't directly testing the thing itself...but is something I'd also like to test):
assert last_response.body.include?("Task saved")
or by somehow testing that a test task's description is now in the YML file. I guess I could open up the YML file and look, and then delete it from the YML file, but I'm pretty sure that's not what I'm supposed to do.
I've confirmed post does correctly save to a YML file:
get('/') do |*user_message|
# prepare erb messages
#user_message = session[:message] if session[:message]
#overlong_description = session[:overlong_description] if
session[:overlong_description]
session[:message] = nil # clear message after being used
session[:overlong_description] = nil # ditto
#tasks = store.all
erb :index #, user_message => {:user_message => params[:user_message]}
end
post('/newtask') do
#task = Task.new(store, params)
# decide whether to save & prepare user messages
if #task.complete == true # task is complete!
#task.message << " " + "Task saved!"
session[:message] = #task.message # use session[:message] for user messages
#task.message = ""
store.save(#task)
else
#task.message << " " + "Not saved." # task incomplete
session[:message] = #task.message # use session[:message] for user messages
session[:overlong_description] = #task.overlong_description if
#task.overlong_description
#task.message = ""
#task.overlong_description = nil
end
redirect '/'
end
As you can see, it ends in a redirect...one response I want to test is actually on the slash route, not on the /newtask route.
So of course the test doesn't work:
def test_post_newtask
post('/newtask', params = {"description"=>"Test task 123"})
# Test that "saved" message for user is in returned page
assert last_response.body.include?("Task saved") # boooo
end
Github source here
If you can give me advice on a book (chapter, website, blog, etc.) that goes over this in a way accessible to a relative beginner, I'd be most grateful.
Be gentle...I'm very new to testing (and programming).
Nobody answered my question and, since I have figured out what the answer is, I thought I would share it here.
First of all, I gather that it shouldn't be necessary to check if the data is actually saved to the YAML store; the main thing is to see if the web page returns the correct result (we assume the database is groovy if so).
The test method I wrote above was correct; it was simply missing the single line follow_redirect!. Apparently I didn't realize that I needed to instruct rake/test to follow the redirect.
Part of the problem was that I simply hadn't found the right documentation. This page does give the correct syntax, but doesn't give much detail. This page helped a lot, and this bit covers redirects.
Here's the updated test method:
def test_post_newtask
post "/newtask", params = {"description" => "Write about quick brown foxes",
"categories" => "writing823"}
follow_redirect!
assert last_response.body.include?("Task saved")
assert last_response.body.include?("Write about quick brown foxes")
end
(With thanks to the Columbus Ruby Brigade.)
In my Sinatra project, I'd like to be able to halt with both an error code and an error message:
halt 403, "Message!"
I want this, in turn, to be rendered in an error page template (using ERB). For example:
error 403 do
erb :"errors/error", :locals => {:message => env['sinatra.error'].message}
end
However, apparently env['sinatra.error'].message (aka the readme and every single website says I should do it) does not expose the message I've provided. (This code, when run, returns the undefined method `message' for nil:NilClass error.)
I've searched for 4-5 hours and experimented with everything and I can't figure out where the message is exposed for me to render via ERB! Does anyone know where it is?
(It seems like the only alternative I can think of is writing this instead of the halt code above, every time I would like to halt:
halt 403, erb(:"errors/error", :locals => {m: "Message!"})
This code works. But this is a messy solution since it involves hardcoding the location of the error ERB file.)
(If you were wondering, this problem is not related to the show_exceptions configuration flag because both set :show_exceptions, false and set :show_exceptions, :after_handler make no difference.)
Why doesn't it work − use the source!
Lets look at the Sinatra source code to see why this problem doesn't work. The main Sinatra file (lib/sinatra/base.rb) is just 2043 lines long, and pretty readable code!
All halt does is:
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
And exceptions are caught with:
# Dispatch a request with error handling.
def dispatch!
invoke do
static! if settings.static? && (request.get? || request.head?)
filter! :before
route!
end
rescue ::Exception => boom
invoke { handle_exception!(boom) }
[..]
end
def handle_exception!(boom)
#env['sinatra.error'] = boom
[..]
end
But for some reason this code is never run (as tested with basic "printf-debugging"). This is because in invoke the block is run like:
# Run the block with 'throw :halt' support and apply result to the response.
def invoke
res = catch(:halt) { yield }
res = [res] if Fixnum === res or String === res
if Array === res and Fixnum === res.first
res = res.dup
status(res.shift)
body(res.pop)
headers(*res)
elsif res.respond_to? :each
body res
end
nil # avoid double setting the same response tuple twice
end
Notice the catch(:halt) here. The if Array === res and Fixnum === res.first part is what halt sets and how the response body and status code are set.
The error 403 { .. } block is run in call!:
invoke { error_block!(response.status) } unless #env['sinatra.error']
So now we understand why this doesn't work, we can look for solutions ;-)
So can I use halt some way?
Not as far as I can see. If you look at the body of the invoke method, you'll see that the body is always set when using halt. You don't want this, since you want to override the response body.
Solution
Use a "real" exception and not the halt "pseudo-exception". Sinatra doesn't seem to come with pre-defined exceptions, but the handle_exception! does look at http_status to set the correct HTTP status:
if boom.respond_to? :http_status
status(boom.http_status)
elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
status(boom.code)
else
status(500)
end
So you could use something like this:
require 'sinatra'
class PermissionDenied < StandardError
def http_status; 403 end
end
get '/error' do
#halt 403, 'My special message to you!'
raise PermissionDenied, 'My special message to you!'
end
error 403 do
'Error message -> ' + #env['sinatra.error'].message
end
Which works as expected (the output is Error message -> My special message to you!). You can return an ERB template here.
In Sinatra v2.0.7+, messages passed to halt are stored in the body of the response. So a halt with an error code and an error message (eg: halt 403, "Message!") can be caught and rendered in an error page template with:
error 403 do
erb :"errors/error", locals: { message: body[0] }
end
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
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