I am trying to mock Stripe::Charge.retrieve in some of my tests, but am getting the error:
Failure/Error: allow_any_instance_of(Stripe::Charge).to receive(:retrieve)
Stripe::Charge does not implement #retrieve
I am guessing the Stripe API does some sort of reflection to generate this method. Is there a way to mock this method? Or maybe a way to turn off the RSpec feature that verifies that mocked methods exist for specific tests?
Stripe::Charge.retrieve is a class method, but you're trying to mock it like an instance method. The biggest tip-off here is that the method's called "retrieve" - you're trying to get a Stripe::Charge object, and it's more idiomatic in Ruby to use a class method for that. Rspec's error isn't very useful; it would be more accurate if it said "Instances of Stripe::Charge do not implement #retrieve".
The good news is you can mock this in Rspec quite straightforwardly:
allow(Stripe::Charge).to receive(:retrieve)
Here's a short example demonstrating this, taken from a Rails project that includes the Stripe gem and rspec-rails. First, a mostly-empty Payment model:
class Payment < ActiveRecord::Base
def get_stripe_charge(payment_ref)
Stripe::Charge.retrieve(payment_ref)
end
end
And now a spec/models/payment_spec.rb to demonstrate the mocking:
require 'rails_helper'
RSpec.describe Payment, type: :model do
it "Allows retrieval of a payment" do
allow(Stripe::Charge).to receive(:retrieve).and_return("Avocados") # A mock object would be more useful here.
payment = Payment.new
expect(payment.get_stripe_charge("whatever")).to eql("Avocados")
end
end
Related
I wrote a simple gem that calls an API. I use this custom gem in a Model Validation to check whether someone exists on the API. If not, there should be a validation error.
I'm now trying to create my RSpec tests, but having some difficulties understanding how to write the unit test.
The validation rule in my User model looks like this:
class User < ApplicationRecord
validate :check_webservice
def check_webservice
result = MyGem::ApiCall(name)
errors.add(:name, "error") unless result.is_valid?
end
end
I tried using VCR in my Unit test to record the API call, but that doesn't seem to work. Any ideas how I should tackle this?
RSpec.describe User, type: :model do
let(:user) { build(:user) }
it "is valid with valid attributes" do
VCR.use_cassette("verified user") do
expect(user).to be_valid
end
end
end
I'm logging the changes to a model to use in an activity feed. I have an observer that does this (activerecord dirty):
def before_update(derp)
#changes = derp.changes
end
This abstracts this functionality out of the controller. I want to test this using rspec, so I have this:
it "assigns derp.changes to #changes" do
derp.attribute = 5
#observer.before_update(derp)
expect(assigns(:changes)).to eq(derp.changes)
end
I'm getting this error: undefined method `assigns' for RSpec::ExampleGroups::DerpObserver::BeforeUpdate:0x007fc6e24eb7f8
How can I use assigns in an observer spec? Or, is there another way that I could test that #changes got assigned?
instance_variable_get is what I was looking for.
http://apidock.com/ruby/Object/instance_variable_get
expect(#observer.instance_variable_get(:#changes)).to eq(derp.changes)
I'm wanting to mock HTTP calls made by Geocoder. I found this post - How can I optionally mock geocoder?
Here is my model
class Listing < ActiveRecord::Base
geocoded_by :address
before_save :geocode, :if => :address_changed?
end
There were two recommendations: VCR (for HTTP requests only?), and Mocha.
I like the looks of Mocha as I can use this for other objects (e.g. models), not just HTTP requests. However, the poster wasn't familiar with Geocoder and didn't know what methods of it to mock, neither do I - still a little new to Rails. Not really sure how/where to trace method calls.
My test looks like this:
test "should create listing" do
sign_in #current_user
assert_difference('Listing.count') do
post :create, listing: valid_params
end
listing = Listing.last
# confirm lat/lng are set
assert_not_nil listing.latitude
assert_not_nil listing.longitude
assert_redirected_to listing_path(assigns(:listing))
end
The test passes, but it can be a little slow to run as it is doing the HTTP request during the test. Would be good to just mock this part in Geocoder.
Alternatively I'll give VCR a go if Geocoder doesn't work with Mocha.
You can stub geocode method on you model it would be enought. For example:
Listing.any_instance.stub(:geocode).and_return([1,1])
Or
allow(Listing).to receive(:geocode).and_return([1,1])
For test unit I guess you should write
Listing.stubs(:geocode).returns([1,1])
I have a callback on my ActiveRecord model as shown below:
before_save :sync_to_external_apis
def sync_to_external_apis
[user, assoc_user].each {|cuser|
if cuser.google_refresh
display_user = other_user(cuser.id)
api = Google.new(:user => cuser)
contact = api.sync_user(display_user)
end
}
end
I would like to write an rspec test which tests that calling save! on an instance of this model causes sync_user to be called on a new Google instance when google_refresh is true. How could I do this?
it "should sync to external apis on save!" do
model = Model.new
model.expects(:sync_to_external_apis)
model.save!
end
As an aside, requesting unreliable resources like the internet during the request-response cycle is a bad idea. I would suggest creating a background job instead.
The usual method for testing is to ensure the results are as expected. Since you're using an API in this case that may complicate things. You may find that using mocha to create a mock object you can send API calls would allow you to substitute the Google class with something that works just as well for testing purposes.
A simpler, yet clunkier approach is to have a "test mode" switch:
def sync_to_external_apis
[ user, assoc_user ].each do |cuser|
if (Rails.env.test?)
#synced_users ||= [ ]
#synced_users << cuser
else
# ...
end
end
end
def did_sync_user?(cuser)
#synced_users and #synced_users.include?(cuser)
end
This is a straightforward approach, but it will not validate that your API calls are being made correctly.
Mocha is the way to go. I'm not familiar with rspec, but this is how you would do it in test unit:
def test_google_api_gets_called_for_user_and_accoc_user
user = mock('User') # define a mock object and label it 'User'
accoc_user = mock('AssocUser') # define a mock object and label it 'AssocUser'
# instantiate the model you're testing with the mock objects
model = Model.new(user, assoc_user)
# stub out the other_user method. It will return cuser1 when the mock user is
# passed in and cuser2 when the mock assoc_user is passed in
cuser1 = mock('Cuser1')
cuser2 = mock('Cuser2')
model.expects(:other_user).with(user).returns(cuser1)
model.expects(:other_user).with(assoc_user).returns(cuser2)
# set the expectations on the Google API
api1 - mock('GoogleApiUser1') # define a mock object and lable it 'GoogleApiUser1'
api2 - mock('GoogleApiUser2') # define a mock object and lable it 'GoogleApiUser2'
# call new on Google passing in the mock user and getting a mock Google api object back
Google.expects(:new).with(:user => cuser1).returns(api1)
api1.expects(:sync_user).with(cuser1)
Google.expects(:new).with(:user => cuser2).returns(api2)
api2.expects(:sync_user).with(cuser2)
# now execute the code which should satisfy all the expectations above
model.save!
end
The above may seem complicated, but it's not once you get the hang of it. You're testing that when you call save, your model does what it is supposed to do, but you don't have the hassle, or time expense of really talking to APIs, instantiating database records, etc.
I haven't been able to find anything for a situation like this. I have a model which has a named scope defined thusly:
class Customer < ActiveRecord::Base
# ...
named_scope :active_customers, :conditions => { :active => true }
end
and I'm trying to stub it out in my Controller spec:
# spec/customers_controller_spec.rb
describe CustomersController do
before(:each) do
Customer.stub_chain(:active_customers).and_return(#customers = mock([Customer]))
end
it "should retrieve a list of all customers" do
get :index
response.should be_success
Customer.should_receive(:active_customers).and_return(#customers)
end
end
This is not working and is failing, saying that Customer expects active_customers but received it 0 times. In my actual controller for the Index action I have #customers = Customer.active_customers. What am I missing to get this to work? Sadly, I'm finding that it's easier to just write the code than it is to think of a test/spec and write that since I know what the spec is describing, just not how to tell RSpec what I want to do.
I think there's some confusion when it comes to stubs and message expectations. Message expectations are basically stubs, where you can set the desired canned response, but they also test for the call to be made by the code being tested. In contrast stubs are just canned responses to the method calls. But don't mix a stub with a message expectation on the same method and test or bad things will happen...
Back to your question, there are two things (or more?) that require spec'ing here:
That the CustomersController calls Customer#active_customers when you do a get on index. Doesn't really matter what Customer#active_customers returns in this spec.
That the active_customers named_scope does in fact return customers where the active field is true.
I think that you are trying to do number 1. If so, remove the whole stub and simply set the message expectation in your test:
describe CustomersController do
it "should be successful and call Customer#active_customers" do
Customer.should_receive(:active_customers)
get :index
response.should be_success
end
end
In the above spec you are not testing what it returns. That's OK since that is the intent of the spec (although your spec is too close to implementation as opposed to behavior, but that's a different topic). If you want the call to active_customers to return something in particular, go ahead and add .and_returns(#whatever) to that message expectation. The other part of the story is to test that active_customers works as expected (ie: a model spec that makes the actual call to the DB).
You should have the array around the mock if you want to test that you receive back an array of Customer records like so:
Customer.stub_chain(:active_customers).and_return(#customers = [mock(Customer)])
stub_chain has worked the best for me.
I have a controller calling
ExerciseLog.this_user(current_user).past.all
And I'm able to stub that like this
ExerciseLog.stub_chain(:this_user,:past).and_return(#exercise_logs = [mock(ExerciseLog),mock(ExerciseLog)])