Cannot get stub verification of should_receive to work when stubbing a method on a stub instance - ruby

I'm trying to stub the facebook graph api that is wrapped by Koala. My goal is to verify that the graph is initialized with the given access token, and the method "me" is called.
My rspec code looks like:
require 'spec_helper'
describe User do
describe '.new_or_existing_facebook_user' do
it 'should get the users info from facebook using the access token' do
# SETUP
access_token = '231231231321'
# build stub of koala graph that expected get_object with 'me' to be called and return an object with an email
stub_graph = stub(Koala::Facebook::API)
stub_graph.stub(:get_object). with('me'). and_return({
:email => 'jame1231231tl#yahoo.com'
})
# setup initializer to return that stub
Koala::Facebook::API.stub(:new) .with(access_token). and_return(stub_graph)
# TEST
user = User.new_or_existing_facebook_user(access_token)
# SHOULD
stub_graph.should_receive(:get_object).with('me')
end
end
end
Model code looks like:
class User < ActiveRecord::Base
# attributes left out for demo
class << self
def new_or_existing_facebook_user(access_token)
#graph = Koala::Facebook::API.new(access_token)
#me = #graph.get_object('me')
# rest of method left out for demo
end
end
end
When running the test, I get the error:
1) User.new_or_existing_facebook_user should get the users info from facebook using the access token
Failure/Error: stub_graph.should_receive(:get_object).with('me')
(Stub Koala::Facebook::API).get_object("me")
expected: 1 time
received: 0 times
# ./spec/models/user_spec.rb:21:in `block (3 levels) in <top (required)>'
How am I stubbing that method wrong?

The should_receive needs to go before the method is called. Rspec message expectations work by taking over the method and listening to it, very similarly to stub. In fact, you can put it in place of your stub.
The expectation will then decide whether it succeeds or not after the rest of the spec is finished.
Try this instead:
describe User do
describe '.new_or_existing_facebook_user' do
it 'should get the users info from facebook using the access token' do
# SETUP
access_token = '231231231321'
# build stub of koala graph that expected get_object with 'me' to be called and return an object with an email
stub_graph = stub(Koala::Facebook::API)
# SHOULD
stub_graph.should_receive(:get_object).with('me').and_return({
:email => 'jamesmyrtl#yahoo.com'
})
# setup initializer to return that stub
Koala::Facebook::API.stub(:new).with(access_token).and_return(stub_graph)
# TEST
user = User.new_or_existing_facebook_user(access_token)
end
end
end

First off, I would not use stub since stub indicates you are most likely not concerned with the behavior of the object. You should use mock instead even though they instantiate the same thing. This more clearly shows you would like to test its behavior.
Your problem comes from that you are setting the expectation after the test. You need to set the expectation before the test in order to have it register.

Related

How to test a Ruby Roda app using RSpec to pass an argument to app.new with initialize

This question probably has a simple answer but I can't find any examples for using Roda with RSpec3, so it is difficult to troubleshoot.
I am using Marston and Dees "Effective Testing w/ RSpec3" book which uses Sinatra instead of Roda. I am having difficulty passing an object to API.new, and, from the book, this is what works with Sinatra but fails with a "wrong number of arguments" error when I substitute Roda.
Depending on whether I pass arguments with super or no arguments with super(), the error switches to indicate that the failure occurs either at the initialize method or in the call to Rack::Test::Methods post in the spec.
I see that in Rack::Test, in the Github repo README, I may have to use Rack::Builder.parse_file("config.ru") but that didn't help.
Here are the two errors that rspec shows when using super without brackets:
Failures:
1) MbrTrak::API POST /users when the user is successfully recorded returns the user id
Failure/Error: post '/users', JSON.generate(user)
ArgumentError:
wrong number of arguments (given 1, expected 0)
# ./spec/unit/app/api_spec.rb:21:in `block (4 levels) in <module:MbrTrak>'
And when using super():
1) MbrTrak::API POST /users when the user is successfully recorded returns the user id
Failure/Error: super()
ArgumentError:
wrong number of arguments (given 0, expected 1)
# ./app/api.rb:8:in `initialize'
# ./spec/unit/app/api_spec.rb:10:in `new'
# ./spec/unit/app/api_spec.rb:10:in `app'
# ./spec/unit/app/api_spec.rb:21:in `block (4 levels) in <module:MbrTrak>'
This is my api_spec.rb:
require_relative '../../../app/api'
require 'rack/test'
module MbrTrak
RecordResult = Struct.new(:success?, :expense_id, :error_message)
RSpec.describe API do
include Rack::Test::Methods
def app
API.new(directory: directory)
end
let(:directory) { instance_double('MbrTrak::Directory')}
describe 'POST /users' do
context 'when the user is successfully recorded' do
it 'returns the user id' do
user = { 'some' => 'user' }
allow(directory).to receive(:record)
.with(user)
.and_return(RecordResult.new(true, 417, nil))
post '/users', JSON.generate(user)
parsed = JSON.parse(last_response.body)
expect(parsed).to include('user_id' => 417)
end
end
end
end
end
And here is my api.rb file:
require 'roda'
require 'json'
module MbrTrak
class API < Roda
def initialize(directory: Directory.new)
#directory = directory
super()
end
plugin :render, escape: true
plugin :json
route do |r|
r.on "users" do
r.is Integer do |id|
r.get do
JSON.generate([])
end
end
r.post do
user = JSON.parse(request.body.read)
result = #directory.record(user)
JSON.generate('user_id' => result.user_id)
end
end
end
end
end
My config.ru is:
require "./app/api"
run MbrTrak::API
Well roda has defined initialize method that receives env as an argument which is being called by the app method of the class. Looks atm like this
def self.app
...
lambda{|env| new(env)._roda_handle_main_route}
...
end
And the constructor of the app looks like this
def initialize(env)
When you run your config.ru with run MbrTrack::API you are actually invoking the call method of the roda class which looks like this
def self.call(env)
app.call(env)
end
Because you have redefined the constructor to accept hash positional argument this no longer works and it throws the error you are receiving
ArgumentError:
wrong number of arguments (given 0, expected 1)
Now what problem are you trying to solve, if you want to make your API class configurable one way to go is to try out dry-configurable which is part of the great dry-ruby gem collection.
If you want to do something else feel free to ask.
It has been a long time since you posted your question so hope you will still find this helpful.

rspec stub to allow [hash_key] to be passed

How do you create a rspec method stub to allow a response from a method that takes in the hash key to return its value?
This is the line I want to test
sub_total = menu.menu_items[item] * quantity
and I'm using this line in rspec as my test stub on a double.
allow(menu).to receive(:menu_items[item]).and_return(2.0)
My env is set up with ruby 2.2.0 and spec 3.1.7
However I keep on getting a
NameError: undefined local variable or method `item'
Ruby code
def place_order(item, quantity, menu)
sub_total = menu.menu_items[item] * quantity
#customer_order << [item, quantity, sub_total]
end
Rspec code
let(:menu) { double :menu }
it "should allow 1 order of beer to placed" do
order = Order.new
allow(menu).to receive(:menu_items[item]).and_return(2.0)
order.place_order(:Beer, 1, 2.0)
expect(order.customer_order).to eq [[:Beer, 1, 2.0]]
end
Failures:
1) Order should allow 1 order of beer to placed
Failure/Error: allow(menu).to receive(:menu_items[item]).and_return(2.0)
NameError:
undefined local variable or method `item' for #<RSpec::ExampleGroups::Order:0x007fbb62917ee8 #__memoized=nil>
# ./spec/order_spec.rb:9:in `block (2 levels) in <top (required)>'
I've tried a number of things but nothing has worked
allow(menu).to receive(:menu_items).and_return(2.0)
allow(menu).to receive(:menu_items).with(item).and_return(2.0)
allow(menu).to receive(:menu_items).with("item").and_return(2.0)
allow(menu).to receive(:menu_items).with([item]).and_return(2.0)
I've run my code in irb and I can see it works but I can't find a way to get my class double to recerive the hash key.
you can do this:
allow(menu.menu_items).to receive(:[]).and_return({Beer: 2.0})
You can also pass an specific item if you need:
allow(menu.menu_items).to receive(:[]).with(1).and_return({Beer: 2.0})
The line menu.menu_items[item] is in reality composed by 3 method calls. [] is a call to the method [] on the Hash returned by menu_items.
I assume menu.menu_items returns a Hash and not an Array, given in the spec item is a Symbol.
That means your stub requires a little bit more work.
allow(menu).to receive(:menu_items).and_return({ Beer: 2.0 })
Also note, the error
undefined local variable or method `item'
is because you were using item in the spec, but item is not defined outside your method.
you're going a little too deep with your stub, think of this instead
allow(menu).to receive(:menu_items).and_return({Beer: 2.0})
Thanks to #SimoneCarletti's answer, I was able to easily stub an instance of PublicActivity. I add this answer only as a more brief (re)statement of the OP's problem and the simplicity of the solution.
Code I want to mimic with a stub:
self.entity = activity.parameters['entity_string']
And the salient parts of the test double:
activity = double('activity') # PublicActivity
allow(activity).to receive(:parameters).and_return({'entity_string' => "some entity name"})
Full code:
class ActivityRenderer
attr_accessor :time
attr_accessor :user
attr_accessor :action
attr_accessor :entity
def initialize(activity)
self.entity = activity.parameters['entity_string']
self.time = activity.updated_at
self.user = User.find(activity.owner_id)
self.action = activity.key
end
end
RSpec.describe ActivityRenderer do
let(:user) { ...factory girl stuff... }
let(:now) { Time.zone.now }
before do
Timecop.freeze
end
it 'provides an activity renderer' do
activity = double('activity') # PublicActivity
allow(activity).to receive(:parameters).and_return({'entity_string' => "some entity name"})
allow(activity).to receive(:updated_at).and_return(now)
allow(activity).to receive(:owner_id).and_return(user._id)
allow(activity).to receive(:key).and_return('some activity?')
ar = ActivityRenderer.new(activity)
expect(ar.user).to eql(user)
expect(ar.time).to eql(now)
expect(ar.action).to eql('some activity?')
expect(ar.entity).to eql("some entity name")
end
end

Mocking in RSpec2 causes singleton can't be dumped in Sinatra controller

I'm trying to write a rspec2 test which gives me an error instead. I know the test is not testing anything particular right now. But I'll add some more code later, I'd like to pass this part first.
context "/login/twitter" do
before(:each) do
request_token = double("request_token")
request_token.stub(:authorize_url).and_return("http://api.twitter.com/oauth/authenticate?oauth_token")
TwitterService.any_instance.stub(:authentication_request_token).and_return(request_token)
get '/login/twitter'
end
it "should redirect to twitter authorized url" do
last_response.header["Location"].should include "http://api.twitter.com/oauth/authenticate?oauth_token"
end
it "should redirect back to home page if error occurs" do
end
end
And this is my controller
get '/login/twitter' do
begin
request_token = TwitterService.new.authentication_request_token
session[:request_token_twitter] = request_token
logger.info(request_token.authorize_url)
redirect request_token.authorize_url
rescue Exception => e
logger.error(e.message)
redirect '/'
end
end
And this is the error I got
1) Server /login/twitter should redirect to twitter authorized url
Failure/Error: get '/login/twitter'
TypeError:
singleton can't be dumped
# ./spec/twitter_route_spec.rb:25:in `block (3 levels) in <top (required)>'
Not sure what I have missed.
In order to stick something in the session it needs to be serialised, and your mock object can't be serialised - the implementation of rspec mocks would appear to use singletons or define singleton methods
You could try and figure out what methods you need to stub out in order to pretend that the object can be dumped (perhaps dump), personally I would just make the test request token be a struct of something similar.

payroll_items_controller_spec.rb:18:in `block (2 levels) displayed in Rspec Controller code

Below is the controller code in rspec for a master item.
To be very frank I'm very new to Ruby with a little knowledge of coding.
require 'spec_helper'
describe PayrollItemsController , "with valid params" do
before(:each) do
#payroll_item = mock_model(PayrollItem, :update_attributes => true)
PayrollItem.stub!(:find).with("1").and_return(#payroll_item)
end
it "should find PayrollItem and return object" do
PayrollItem.should_receive(:find).with("0").and_return(#payroll_item)
end
it "should update the PayrollItem object's attributes" do
#payroll_item.should_receive(:update_attributes).and_return(true)
end
end
When I run the controller code, following error displayed:
(Mock "PayrollItem_1001").update_attributes(any args)
expected: 1 time
received: 0 times
./payroll_items_controller_spec.rb:18:in `block (2 levels) in '
You have to actually make a request (get, post, put etc.) to the controller in order for the mock to have anything to check.
So for example:
it "should find PayrollItem and return object" do
PayrollItem.should_receive(:find).with("0").and_return(#payroll_item)
put :update, :id => "0"
end
In addition to that, looking at your code, you have some inconsistencies with your return values: in your before block you're stubbing PayrollItem.find with an id of 1 to return something, and then in your first spec you're mocking it with an id of 0 to return the same thing.
It's fine to both stub and mock the same method because they fulfill different functions: a stub makes sure that the code runs smoothly, while the mock actually checks an expectation. However, you should be stubbing/mocking it for the same argument, so that all the specs using this before block are testing the same thing.

What is the best way to mock a 3rd party object in ruby?

I'm writing a test app using the twitter gem and I'd like to write an integration test but I can't figure out how to mock the objects in the Twitter namespace. Here's the function that I want to test:
def build_twitter(omniauth)
Twitter.configure do |config|
config.consumer_key = TWITTER_KEY
config.consumer_secret = TWITTER_SECRET
config.oauth_token = omniauth['credentials']['token']
config.oauth_token_secret = omniauth['credentials']['secret']
end
client = Twitter::Client.new
user = client.current_user
self.name = user.name
end
and here's the rspec test that I'm trying to write:
feature 'testing oauth' do
before(:each) do
#twitter = double("Twitter")
#twitter.stub!(:configure).and_return true
#client = double("Twitter::Client")
#client.stub!(:current_user).and_return(#user)
#user = double("Twitter::User")
#user.stub!(:name).and_return("Tester")
end
scenario 'twitter' do
visit root_path
login_with_oauth
page.should have_content("Pages#home")
end
end
But, I'm getting this error:
1) testing oauth twitter
Failure/Error: login_with_oauth
Twitter::Error::Unauthorized:
GET https://api.twitter.com/1/account/verify_credentials.json: 401: Invalid / expired Token
# ./app/models/user.rb:40:in `build_twitter'
# ./app/models/user.rb:16:in `build_authentication'
# ./app/controllers/authentications_controller.rb:47:in `create'
# ./spec/support/integration_spec_helper.rb:3:in `login_with_oauth'
# ./spec/integration/twit_test.rb:16:in `block (2 levels) in <top (required)>'
The mocks above are using rspec but I'm open to trying mocha too. Any help would be greatly appreciated.
OK, I managed to figure this out thanks to everyone's help. Here's the final test:
feature 'testing oauth' do
before(:each) do
#client = double("Twitter::Client")
#user = double("Twitter::User")
Twitter.stub!(:configure).and_return true
Twitter::Client.stub!(:new).and_return(#client)
#client.stub!(:current_user).and_return(#user)
#user.stub!(:name).and_return("Tester")
end
scenario 'twitter' do
visit root_path
login_with_oauth
page.should have_content("Pages#home")
end
end
The trick was figuring out that I needed to stub :configure and :new on the real objects and stub :current_user and :name on a dobuled object instance.
I think the problem is just the way you are using the mock, you created the mock #twitter, but you never actually use it. I think you may be under the impression that any calls to Twitter will use the stubbed methods you specified, but that's not how it works, only calls made to #twitter are stubbed.
I use double ruby, not rspec mocks, but i believe you want to do something like this instead:
Twitter.stub!(:configure).and_return true
...
Twitter::Client.stub!(:current_user).and_return #user
This ensures that anytime the methods you stubbed on Twitter, Twitter::Client are called, they respond how you want.
Also, it seems strange that this is tested as part of a view, should really be part of a controller test instead unless i'm missing something.
You can try using something like http://jondot.github.com/moxy/ . Mock Web Requests

Resources