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)
Related
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
I am in the process of refactoring a bloated controller that serves a polymorphic model for carousels. I am trying to build a class method that handles finding and returning the item that is carouselable.
In my RSPEC tests I want to stub the method, 'is_something?' on the venue that is found as a result of the params.
def self.find_carouselable(params)
.......
elsif params[:venue_id].present?
venue=Venue.friendly.find(params[:venue_id])
if venue.is_something?
do this
else
do that
end
end
end
I cant work out how to stub an object that is created as a result of the inputted data - I am not sure if this is called stubbing or mocking?
context "carouselable is a venue" do
before do
allow(the_venue).to receive(:is_something?).and_return(true)
end
it "returns the instance of the carouselable object" do
expect(CopperBoxCarouselItem.find_carouselable(venue_params)).to eq the_venue
end
end
many thanks
You should be able to do:
allow_any_instance_of(Venue).to receive(:is_something?).and_return(true)
https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/allow-a-message-on-any-instance-of-a-class
You only need to stub the Venue bit, like so
before do
allow(Venue).to receive(:friendly).and_return(some_venues)
allow(some_venues).to receive(:find).and_return(venue)
allow(venue).to receive(:is_something?).and_return(true)
end
How can you test the presence of a callback in your model, specifically one that's triggered by creating a record, such as after_create or after_commit on: :create?
Here's an example callback with the (empty) method that it calls.
# app/models/inbound_email.rb
class InboundEmail < ActiveRecord::Base
after_commit :notify_if_spam, on: :create
def notify_if_spam; end
end
Here's the pending spec, using RSpec 3.
# spec/models/inbound_email_spec.rb
describe InboundEmail do
describe "#notify_if_spam" do
it "is called after new record is created"
end
end
Using a message expectation to test that the method is called seems like the way to go.
For example:
expect(FactoryGirl.create(:inbound_email)).to receive(:notify_if_spam)
But that doesn't work. Another way is to test that when a record is created, something inside the called method happens (e.g. email sent, message logged). That implies that the method did get called and therefore the callback is present. However, I find that a sloppy solution since you're really testing something else (e.g. email sent, message logged) so I'm not looking for solutions like that.
I think Frederick Cheung is right. This should work. The problem with your example is that the callback has already been called before the expectation has been set.
describe InboundEmail do
describe "#notify_if_spam" do
it "is called after new record is created" do
ie = FactoryGirl.build(:inbound_email)
expect(ie).to receive(:notify_if_spam)
ie.save!
end
end
end
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'm trying to stub out a method on my current_user (using a modified restful_authentication auth solution) with rspec. I'm completely unsure of how I can access this method in my controller specs. current_user by itself doesn't work. Do I need to get the controller itself first? How do I do this?
Using rails 2.3.5, rspec 1.3.0 and rspec-rails 1.3.2
# my_controller_spec.rb
require 'spec_helper'
describe MyController do
let(:foos){ # some array of foos }
it "fetches foos of current user" do
current_user.should_receive(:foos).and_return(foos)
get :show
end
end
Produces
NoMethodError in 'ChallengesController fetches foos of current user'
undefined method `current_user' for #<Spec::Rails::Example::ControllerExampleGroup::Subclass_1::Subclass_1::Subclass_2::Subclass_2:0x7194b2f4>
rspec-rails gives you a controller method for use in controller examples. So:
controller.stub!(:current_user).with(:foos).and_return(foos)
ought to work.
how can it know where to find current_user? this should solve it:
subject.current_user.should_receive(:foos).and_return(foos)
I'm not entirely familiar with restful_authentication, just Authlogic and Devise, but it's probably similar in that current_user is a controller method and not an object, which is why calling should_receive on it isn't working as expected (you're setting an expectation on the object that current_user returns, but the method isn't accessible inside the scope of your expectation).
Try this:
stub!(:current_user).and_return(foos)
I read this and tweaked mine a bit. If you simply want to pass in a user object into your Rspec test, you can use this:
First, create a user object within the rspec test. For example:
(use whatever attributes you need or are required to create the user object.)
user = User.create(name: "ted")
(Note: you can also use a factory from FactoryGirl.)
Now, with that user object which is saved into the variable "user", do this within that same Rspec test:
controller.stub!(:current_user).and_return(user)
that should work...