What is the best way to mock a 3rd party object in ruby? - 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

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.

Sinatra unit test - post with JSON body

I am trying to build a unit test for a REST API I built using Sinatra. For right now I just want to test that my echo function works right. Echo uses POST and will return the exact same payload from the post. I am still new with ruby, so forgive me if I don't use the proper lingo.
Here is the code I want to test:
post '/echo' do
request.body.read
end
This is the unit test I am trying to make:
ENV['RACK_ENV'] = 'test'
require './rest_server'
require 'test/unit'
require 'rack/test'
require 'json'
class RestServer < Test::Unit::TestCase
def app
Sinatra::Application
end
def test_check_methods
data = '{"dataIn": "hello"}'
response = post '/echo', JSON.parse(data)
assert.last_response.ok?
assert(response.body == data)
end
end
With the above code, here is the error:
NoMethodError: undefined method `dataIn' for Sinatra::Application:Class
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1285:in `block in compile!'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1285:in `each_pair'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1285:in `compile!'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1267:in `route'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1256:in `post'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1688:in `block (2 levels) in delegate'
/Users/barrywilliams/RubymineProjects/project/rest_server_test.rb:20:in `test_check_methods'
If I try doing it without the JSON.parse, I get
NoMethodError: undefined method `key?' for "{\"dataIn\": \"hello\"}":String
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1265:in `route'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1256:in `post'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1688:in `block (2 levels) in delegate'
/Users/barrywilliams/RubymineProjects/project/rest_server_test.rb:20:in `test_check_methods'
If I try doing it where data = 'hello', then I get the same undefined method 'key?' error
I've tried this suggestion, with no success:
http://softwareblog.morlok.net/2010/12/18/testing-post-with-racktest/
I get an error saying that post only takes 2 arguments, not 3.
So, in summary, I need to be able to make a call, have the code I'm testing receive the call and return a response, then I need to be able to read that response and verify it was the original data. Right now it looks like it's getting stuck at just making the call.
I did a thing a little similar, it might help you :
The application post definition :
post '/' do
data = JSON.parse request.body.read.to_s
"Hello !\n#{data.to_s}"
end
The .to_s is necessary, else the conversions will not be exactly the same :-/
Then on the test file :
class RootPostTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_return_the_parameters
data = {
'reqID' => 1,
'signedReqID' => "plop",
'cert' => "mycert"
}
post '/', data.to_json, "CONTENT_TYPE" => "application/json"
assert last_response.ok?
body_espected = "Hello !\n#{JSON.parse(data.to_json).to_s}"
assert_equal last_response.body, body_espected
end
end
Hope it helped you.
Rack Test will give you back the response body in last_response.body, no need to save it to a variable. You're also not echoing back what you've sent - data in the code you've given is JSON, but you converted it to a hash and posted that, so it's not going to match what comes back. Either send JSON, or convert it to JSON in the Sinatra route if you want to do that (see https://stackoverflow.com/a/12138793/335847 for more).
In the Sinatra app:
require 'json'
post '/echo' do
# Don't use request.body.read as you're not posting JSON
params.to_json
end
and in the test file:
def test_check_methods
data = '{"dataIn": "hello"}'
post '/echo', JSON.parse(data)
assert.last_response.ok?
assert(last_response.body == data)
end
If you do end up wanting to post JSON (which I think is usually not a good idea if it's easy to convert or already have the data as a hash) then use :provides => "json" as a condition to the route, and consider using Rack::Test::Accepts to make life easier writing the test for that (note: that's a shameless plug for a gem I wrote;)

Using specific VCR cassette based on request

Situation: testing a rails application using Rspec, FactoryGirl and VCR.
Every time a User is created, an associated Stripe customer is created through Stripe's API. While testing, it doesn't really makes sense to add a VCR.use_cassette or describe "...", vcr: {cassette_name: 'stripe-customer'} do ... to every spec where User creation is involved. My actual solution is the following:
RSpec.configure do |config|
config.around do |example|
VCR.use_cassette('stripe-customer') do |cassette|
example.run
end
end
end
But this isn't sustainable because the same cassette will be used for every http request, which of course is very bad.
Question: How can I use specific fixtures (cassettes) based on individual request, without specifying the cassette for every spec?
I have something like this in mind, pseudo-code:
stub_request(:post, "api.stripe.com/customers").with(File.read("cassettes/stripe-customer"))
Relevant pieces of code (as a gist):
# user_observer.rb
class UserObserver < ActiveRecord::Observer
def after_create(user)
user.create_profile!
begin
customer = Stripe::Customer.create(
email: user.email,
plan: 'default'
)
user.stripe_customer_id = customer.id
user.save!
rescue Stripe::InvalidRequestError => e
raise e
end
end
end
# vcr.rb
require 'vcr'
VCR.configure do |config|
config.default_cassette_options = { record: :once, re_record_interval: 1.day }
config.cassette_library_dir = 'spec/fixtures/cassettes'
config.hook_into :webmock
config.configure_rspec_metadata!
end
# user_spec.rb
describe :InstanceMethods do
let(:user) { FactoryGirl.create(:user) }
describe "#flexible_name" do
it "returns the name when name is specified" do
user.profile.first_name = "Foo"
user.profile.last_name = "Bar"
user.flexible_name.should eq("Foo Bar")
end
end
end
Edit
I ended doing something like this:
VCR.configure do |vcr|
vcr.around_http_request do |request|
if request.uri =~ /api.stripe.com/
uri = URI(request.uri)
name = "#{[uri.host, uri.path, request.method].join('/')}"
VCR.use_cassette(name, &request)
elsif request.uri =~ /twitter.com/
VCR.use_cassette('twitter', &request)
else
end
end
end
VCR 2.x includes a feature specifically to support use cases like these:
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/before-http-request-hook!
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/after-http-request-hook!
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/around-http-request-hook!
VCR.configure do |vcr|
vcr.around_http_request(lambda { |req| req.uri =~ /api.stripe.com/ }) do |request|
VCR.use_cassette(request.uri, &request)
end
end
IMO, libraries like this should provided you with a mock class, but w/e.
You can do your pseudocode example already with Webmock, which is the default internet mocking library that VCR uses.
body = YAML.load(File.read 'cassettes/stripe-customer.yml')['http_interactions'][0]['response']['body']['string']
stub_request(:post, "api.stripe.com/customers").to_return(:body => body)
You could put that in a before block that only runs on a certain tag, then tag the requests that make API calls.
In their tests, they override the methods that delegate to RestClient (link). You could do this as well, take a look at their test suite to see how they use it, in particular their use of test_response. I think this is a terribly hacky way of doing things, and would feel really uncomfortable with it (note that I'm in the minority with this discomfort) but it should work for now (it has the potential to break without you knowing until runtime). If I were to do this, I'd want to build out real objects for the two mocks (the one mocking rest-client, and the other mocking the rest-client response).
The whole point (mostly anyway) of VCR to just to replay the response of a previous request. If you are in there picking and choosing what response goes back to what request, you are quote/unquote doing-it-wrong.
Like Joshua already said, you should use Webmock for something like this. That's what VCR is uing behind the scenes anyway.

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

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.

Testing #current_user method using RSpec

I've been trying to do this for a couple of days now, but I can't figure it out. I have the following code in my controller:
#some_object = #current_user.some_method
In my spec, I want to attach a should_receive hook on that method, but I can't make it work. I've tried all of these, but none of them work:
assigns[:current_user].should_receive(:some_method).at_least(:once) # expected 1, got 0
User.should_receive(:some_method).at_least(:once) # expected 1, got 0
How is the correct way of testing this? I'm running this in my spec, and login is working:
setup :activate_authlogic
...
UserSession.create(users(:rune))
Thanks!
One example comes from the Ruby on Rails Tutorial. Rather than setting and reading #current_user directly, it defines two helper methods:
def current_user=(user)
#current_user = user
end
def current_user
#current_user
end
Later, they access this method in the tests using the controller method:
def test_sign_in(user)
controller.current_user = user
end
Using this methodology, you should be able to use
controller.current_user.should_receive(:some_method).at_least(:once)
You can’t call something like in the controllers:
expect(current_user).to be_present
expect(user_signed_in?).to be_true
So to do so, you can do this :
module ControllerMacros
def current_user
user_session_info = response.request.env['rack.session']['warden.user.user.key']
if user_session_info
user_id = user_session_info[0][0]
User.find(user_id)
else
nil
end
end
def user_signed_in?
!!current_user
end
end
You can either include the ControllerMacros in the top of the controller spec or include it in the spec_helper.rb like so :
RSpec.configure do |config|
config.include ControllerMacros, type: :controller
end

Resources