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
Related
I have a simple model that has an after_save callback to update some things on a related model when the status changes:
class Model < ApplicationRecord
has_many :related_records
after_update :check_change
enum :status { 'status1', 'status2'}
def check_change
return unless saved_change_to_status?
related_records.each(&:schedule_email)
end
Now I'd like to test this method with Rspec. I first tested whether the method gets called, which succeeds:
it "should be called"
expect(subject).to receive(:check_change)
subject.save
end
Now I'd like to check whether each of the related records receives the schedule_email method:
it 'should call method on related records' do
subject.update(status: 'status2')
subject.emails.each do |msg|
expect(msg).to receive(:schedule_email)
end
end
Somehow this doesn't work. I also tried calling the update after the expect without any luck. I can't get my head around what is going wrong. Any ideas?
Edit: I also created a break point after the guard clause in the method. During the test it stops execution, so I'm assuming that the schedule_email method is being called on all related records... should I expect in a different way?
expect(msg).to receive(:schedule_email) should go BEFORE actual test call, to set message expectations properly. So it would rather be:
it 'should call method on related records' do
subject.emails.each do |msg|
expect(msg).to receive(:schedule_email)
end
subject.update(status: 'status2')
end
I am trying to write two RSpec tests for two different problems that are much more advanced that what I'm used to writing.
What I'm trying to test within my controller:
def index
#buildings ||= building_class.active.where(place: current_place)
end
My attempt at writing the RSpec test:
describe 'GET :index' do
it "assigns #buildings" do
#buildings ||= building_class.active.where(place: current_place)
get :index
expect(assigns(:buildings)).to eq([building])
end
end
This test failed and wouldn't even run so I know I'm missing something.
My second test is needing to test the returned value of a class method. Here is what I am needing to test within the controller:
def class_name
ABC::Accountant::Business
end
Here is my attempt at testing this method:
describe "class name returns ABC::Accountant::Business" do
subject do
expect(subject.class_name).to eq(ABC::Accountant::Business)
end
end
For the first test I would do something like this:
First, I would move that .active.where(place: current_place) to a scope (I'm guessing building_class returns Building or something like that):
class Building << ApplicationRecord
scope :active_in, -> (place) { active.where(place: place)
Then it's easier to stub for the test
describe 'GET :index' do
it "assigns #buildings" do
scoped_buildings = double(:buildings)
expect(Building).to receive(:active_in).and_return(scoped_buildings)
get :index
expect(assigns(:buildings)).to eq(scoped_buildings)
end
end
Then your controller will do
#buildings ||= building_class.active_in(current_place)
This way you are testing two things: that the controller actually calls the scope and that the controller assigns the returned value on the #buildings variable (you don't really need to test the actual buidlings, you can test the scope on the model spec).
Personally, I feel like it would be better to do something like #buildings = current_place.active_buildings using the same idea of the scope to test that you are getting the active buildings of the current place.
EDIT: if you can't modify your controller, then the stubbing is a little different and it implies some chaining of methods that I don't like to explicitly test.
scoped_buildings = double(:buildings)
controller.stub_chain(:building_class, :active, :where).and_return(scoped_building)
get :index
expect(assings(:buildings)).to eq scoped_buildings
Note that now your test depends on a specific implementation and testing implementation is a bad practice, one should test behaviour and not implementation.
For the second, I guess something like this should work:
describe ".class_name" do
it "returns ABC::Accountant::Business" do
expect(controller.class_name).to eq(ABC::Accountant::Business)
end
end
IMHO, that the method's name if confusing, class_name gives the idea that it returns a string, you are not returnin a name, you are returning a class. Maybe you can change that method to resource_class or something less confusing.
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 have this class:
class EnablePost
def initialize(post_klass, id)
raise "oops" if post_klass.blank?
#post_klass = post_klass
#id = id
end
def perform
post = #post_klass.find_by_id(#id)
return unless post
post.update_attribute :enabled, true
end
end
The spec I have to write to test the above:
describe EnablePost do
it "should enable a post" do
post = mock
post.should_receive(:blank?).and_return(false)
post.should_receive(:find_by_id).with(22).and_return(post)
post.should_receive(:update_attribute).with(:enabled, true)
result = EnablePost.new(Post, 22).perform
result.should be_true
end
end
But what I really want to do is treat EnablePost as a black box. I don't want to have to mock :blank?, :find_by_id or :update_attribute.
That is to say I want my spec to look like:
describe EnablePost do
it "should enable a post" do
post = mock
result = EnablePost.new(post, 22).perform
result.should be_true
end
end
What am I missing here? Am I using mocks incorrectly?
Yes, you're confusing mocks and stubs.
A good mock explanation: http://jamesmead.org/talks/2007-07-09-introduction-to-mock-objects-in-ruby-at-lrug/
Mocks:
Different things to different people
Ambiguous terminology
Confusion with Rails “mocks”
Mock Object:
Expected method invocations set in advance
Verifies actual invocations match expected ones
Also check out http://martinfowler.com/articles/mocksArentStubs.html [thanks to user Zombies in the comments]
If you're using RSpec, it aliases double, mock, and stub. RSpec expects you to choose whichever method name makes your code clearest.
Your first chunk of test code is using the word "mock" correctly. You're setting up the method invocations that you expect to be called, in advance, and then performing them.
However, you're testing two different areas of your code: the first area is the initialize method, the second is the #perform method.
You may find it easier to mock and stub if you write smaller methods:
# What you want to test here is the raise and the member variables.
# You will stub the post_klass.
def initialize(post_klass, post_id) # post_id is a better name
raise "oops" if post_klass.blank?
#post_klass = post_klass
#post_id = post_id # because we don't want to mask Object#id
end
attr_accessor :post_id
attr_accessor :post_klass
# What you want to test here is the post_klass calls #find_by_id with post_id.
# See we've changed from using instance variables to methods.
def post
post_klass.find_by_id(post_id)
end
# What you want to test here is if the update happens.
# To test this, stub the #post method.
def perform
p = post
return unless p
p.update_attribute :enabled, true
end
When you write your code this way, you make it easy to stub the #post method.
See this for RSpec example source code showing the difference between mock and stub:
http://blog.firsthand.ca/2011/12/example-using-rspec-double-mock-and.html
This should be simple, but i can't get it to work. I want to stub an :
#alliance.save
so that it returns true. I tried :
Alliance.stub(:save).and_return(true)
but it won't work. Any ideas ?
If I'm not mistaken, Alliance.stub(:save) would affect calls to Alliance.save. You want #alliance.stub(:save).and_return(true).
Mocha has a useful method any_instance, so you could do something like Alliance.any_instance.stubs(:save).returns(true), which would (as the name implies) stub the save method for any instance of Alliance.
Using the new RSpec syntax:
allow_any_instance_of(Alliance).to receive(:save).and_return(true)
You're probably looking for something like:
describe AllianceController do
let(:alliance) { mock_model(Alliance) }
describe "#<controller action>" do
before do
Alliance.stub :new => alliance
end
context "valid alliance" do
before do
alliance.stub :save => true
end
it "should ..." do
end
end
end
end
The inner context allows you to work with an Alliance mock which has the save method stubbed to return true.