Rspec:: allow every instance to receive a message - ruby-on-rails

I want to mock a method for every instance of a class.
if I allow_any_instance_of then it works great if instance_count = 1
However if I have many instances of the same class the second instance isn't caught by the mock.
I'm attempting to get a pile of tokens from different sites. But during testing I don't really need "real" tokens. So I plan to mock get_token to return '1111'.
class Foo
def children
[Bar.new, Bar.new] #....
end
def get_tokens
children.map(&:get_token) || []
end
end
so now how do I not mock out the get_tokens?

How about solution like this:
require "spec_helper"
require "ostruct"
class Bar
def get_token
("a".."f").to_a.shuffle.join # simulating randomness
end
end
class Foo
def children
[Bar.new, Bar.new, Bar.new]
end
def get_tokens
children.map(&:get_token) || []
end
end
RSpec.describe Foo do
before do
allow(Bar).to receive(:new).and_return(OpenStruct.new(get_token: "123"))
end
it "produces proper list of tokens" do
expect(Foo.new.get_tokens).to eq ["123", "123", "123"]
end
end
We're stubbing new method on Bar to return something that quacks with get_token (so it behaves like Bar), and it returns a fixed string. This is something you can relay on.
Hope that helps!

Related

Dynamically defining a Object#initialize for a Ruby class

In my code base, I have a bunch of objects that all adhere to the same interface, which consists of something like this:
class MyTestClass
def self.perform(foo, bar)
new(foo, bar).perform
end
def initialize(foo, bar)
#foo = foo
#bar = bar
end
def perform
# DO SOMETHING AND CHANGE THE WORLD
end
end
The differentiating factor between the classes is the arity of the self.perform and initialize, plus the body of the #perform class.
So, I'd like to be able to create an ActiveSupport::Concern (or just a regular Module if that would work better) which allowed me to do something like this:
class MyTestClass
inputs :foo, :bar
end
which would then use some meta-programming to define self.perform and initialize of the above methods whose airty would depend on the airty specified by the self.inputs method.
Here is what I have so far:
module Commandable
extend ActiveSupport::Concern
class_methods do
def inputs(*args)
#inputs = args
class_eval %(
class << self
def perform(#{args.join(',')})
new(#{args.join(',')}).perform
end
end
def initialize(#{args.join(',')})
args.each do |arg|
instance_variable_set(##{arg.to_s}) = arg.to_s
end
end
)
#inputs
end
end
end
This seems to get the arity of the methods correct, but I'm having a tough time figuring out how to handle the body of the #initialize methods.
Can anybody help me figure out a way that I can successfully meta-program the body of #initialize so it behaves like the example I provided?
You could use this as body for #initialize:
#{args}.each { |arg| instance_variable_set("#\#{arg}", arg) }
However, I wouldn't string eval it. It usually leads to evil things. That said, here is an implementation which gives an incorrect Foo.method(:perform).arity, but still behaves as you would expect:
module Commandable
def inputs(*arguments)
define_method(:initialize) do |*parameters|
unless arguments.size == parameters.size
raise ArgumentError, "wrong number of arguments (given #{parameters.size}, expected #{arguments.size})"
end
arguments.zip(parameters).each do |argument, parameter|
instance_variable_set("##{argument}", parameter)
end
end
define_singleton_method(:perform) do |*parameters|
unless arguments.size == parameters.size
raise ArgumentError, "wrong number of arguments (given #{parameters.size}, expected #{arguments.size})"
end
new(*parameters).perform
end
end
end
class Foo
extend Commandable
inputs :foo, :bar
def perform
[#foo, #bar]
end
end
Foo.perform 1, 2 # => [1, 2]
You were so close! instance_variable_set takes two arguments, first is the instance variable and second is the value you want to set it to. You also need to get the value of the variable, which you can do using send.
instance_variable_set(##{arg.to_s}, send(arg.to_s))

Rspec controller test - how to stub and/or test custom objects

Below is passing!
Controller code:
class OrdersController
def create
...
#order.save
end
end
Spec code:
describe OrdersController do
it "should call save method" do
Order.any_instance.should_receive(:save)
post :create
end
end
But if only it were that easy... I have some custom job objects that are executed after the save, so the code actually looks like this:
Controller code:
class OrdersController
def create
...
#order.save
RoadrunnerEmailAlert.new.async.perform(#order.id, true)
CalendarInvite.new.async.perform(#order.id)
RoadrunnerTwilioAlert.new.async.perform(#order.id)
end
end
I would love to test that the custom objects are receiving the chain of methods with the right parameters, but not sure how, short of creating something in the spec code like this:
before do
class RoadrunnerEmailAlert
def async
end
end
end
But that's so contrived, it certainly isn't right... advice appreciated!
In case this helps other people... this is a very comprehensive answer.
Context & design notes
The async framework is Sucker Punch gem
(http://brandonhilkert.com/blog/why-i-wrote-the-sucker-punch-gem/).
Back then, this was the easiest thing for me to use after looking at
Delayed Job, Sidekick, etc
Basically it works like this: in Controller reference a Job that then references anything else (in my case, some POROs)
If I were really rigidly testing, I'd want to test that A) the Controller calls the Job appropriately and passes the right parameters, and B) the Job calls the appropriate POROs and passes the right parameters. But instead, I just tested that the Controller calls the appropriate POROs and passes the right parameters, i.e., the Jobs are already working.
Controller code
#order.save
RoadrunnerEmailAlert.new.async.perform(#order.id, true)
CalendarInvite.new.async.perform(#order.id)
RoadrunnerTwilioAlert.new.async.perform(#order.id)
Job code
# app/jobs/roadrunner_email_alert.rb
class RoadrunnerEmailAlert
include SuckerPunch::Job
def perform(order_id, require_tos)
ActiveRecord::Base.connection_pool.with_connection do
OrderMailer.success_email(order_id, require_tos).deliver
end
end
end
# app/jobs/calendar_invite.rb
class CalendarInvite
include SuckerPunch::Job
def perform(order_id)
ActiveRecord::Base.connection_pool.with_connection do
CreateCalendar.new(order_id).perform
end
end
end
# app/jobs/roadrunner_twilio_alert.rb
class RoadrunnerTwilioAlert
include SuckerPunch::Job
def perform(order_id)
ActiveRecord::Base.connection_pool.with_connection do
CreateAlert.new(order_id).perform
end
end
end
Test code
The really big thing here that I don't know why I keep forgetting (but only in testing) is class vs. instance of class. For the POROs, since I'm instantiating the object, I needed to test 2 different "layers" (first that the object is instantiated appropriately, second that the instantiated object is acted upon appropriately).
require 'sucker_punch/testing/inline'
describe "Controller code" do
before do
OrderMailer.any_instance.stub(:success_email)
mock_calendar = CreateCalendar.new(1)
CreateCalendar.stub(:new).and_return(mock_calendar)
CreateCalendar.any_instance.stub(:perform)
mock_alert = CreateAlert.new(1)
CreateAlert.stub(:new).and_return(mock_alert)
CreateAlert.any_instance.stub(:perform)
end
it "should call appropriate async jobs" do
expect_any_instance_of(OrderMailer).to receive(:success_email).with(1, true)
expect(CreateCalendar).to receive(:new).with(1)
expect_any_instance_of(CreateCalendar).to receive(:perform)
expect(CreateAlert).to receive(:new).with(1)
expect_any_instance_of(CreateAlert).to receive(:perform)
post :create
end
end

How to stub a method on a model copy in rspec?

Say I have the next code:
class Foo < ActiveRecord::Base
def self.bar
all.each(&:bar)
end
def bar
# do something that I want stub in test
end
end
Now I want to create test (Rspec):
foo = Foo.create
expect(foo).to receive(:bar)
Foo.bar
This test does not pass because Foo.bar calls other instance of the same model foo.
I wrote some complex code in such situations before, like:
expect_any_instance_of(Foo).to receive(:bar)
but this is not good, because there are no confidence that foo receives message (there could be several instances). And also expect_any_instance_of is not recommended by Rspec maintainers.
How do you test such code, is any best practice?
If you want fine grained control over each instance, you can do something like this:
foo_1 = Foo.create
expect(foo_1).to receive(:bar).and_return(1)
foo_2 = Foo.create
expect(foo_2).to receive(:bar).and_return(2)
# This makes it so our specific instances of foo_1 and foo_2 are returned.
allow(Foo).to receive(:all).and_return([foo_1, foo_2])
expect(Foo.bar).to eq [1, 2]
In your example:
class Foo < ActiveRecord::Base
def self.bar
all.map(&:bar)
end
def bar
# do something that I want stub in test
end
end
If foo = Foo.new
Note that foo.bar is different from Foo.bar.
The former is calling the instance method def bar ( which you want stubed ), while the later is calling the class method def self.bar.
In your test however,
foo = Foo.create
expect(foo).to receive(:bar)
Foo.bar
You are attempting to stub the instance method def bar ( expect(foo).to receive(:bar) ), while you are calling the class method def self.bar ( Foo.bar )
That is why it seems not to work.

Mocking/stubbing a method that's included from "instance.extend(DecoratorModule)"

I use a decorator module that get's included in a model instance (through the "extends" method). So for example :
module Decorator
def foo
end
end
class Model < ActiveRecord::Base
end
class ModelsController < ApplicationController
def bar
#model = Model.find(params[:id])
#model.extend(Decorator)
#model.foo
end
end
Then I would like in the tests to do the following (using Mocha) :
test "bar" do
Model.any_instance.expects(:foo).returns("bar")
get :bar
end
Is this possible somehow, or do you have in mind any other way to get this functionality???
Just an Assumption Note: I will assume that your Decorator foo method returns "bar" which is not shown in the code that you sent. If I do not assume this, then expectations will fail anyway because the method returns nil and not "bar".
Assuming as above, I have tried the whole story as you have it with a bare brand new rails application and I have realized that this cannot be done. This is because the method 'foo' is not attached to class Model when the expects method is called in your test.
I came to this conclusion trying to follow the stack of called methods while in expects. expects calls stubs in Mocha::Central, which calls stubs in Mocha::ClassMethod, which calls *hide_original_method* in Mocha::AnyInstanceMethod. There, *hide_original_method* does not find any method to hide and does nothing. Then Model.foo method is not aliased to the stubbed mocha method, that should be called to implement your mocha expectation, but the actual Model.foo method is called, the one that you dynamically attach to your Model instance inside your controller.
My answer is that it is not possible to do it.
It works (confirmed in a test application with render :text)
I usually include decorators (instead of extending them at runtime) and I avoid any_instance since it's considered bad practice (I mock find instead).
module Decorators
module Test
def foo
"foo"
end
end
end
class MoufesController < ApplicationController
def bar
#moufa = Moufa.first
#moufa.extend(Decorators::Test)
render :text => #moufa.foo
end
end
require 'test_helper'
class MoufesControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "bar" do
m = Moufa.first
Moufa.expects(:find).returns(m)
m.expects(:foo).returns("foobar")
get :bar, {:id => 32}
assert_equal #response.body, "foobar"
end
end
Ok, now I understand. You want to stub out a call to an external service. Interesting that mocha doesn't work with extend this way. Besides what is mentioned above, it seems to be because the stubbed methods are defined on the singleton class, not the module, so don't get mixed in.
Why not something like this?
test "bar" do
Decorator = Module.new{ def foo; 'foo'; end }
get :bar
end
If you'd rather not get the warnings about Decorator already being defined -- which is a hint that there's some coupling going on anyway -- you can inject it:
class ModelsController < ApplicationController
class << self
attr_writer :decorator_class
def decorator_class; #decorator_class ||= Decorator; end
end
def bar
#model = Model.find(params[:id])
#model.extend(self.class.decorator_class)
#model.foo
end
end
which makes the test like:
test "bar" do
dummy = Module.new{ def foo; 'foo'; end }
ModelsController.decorator_class = dummy
get :bar
end
Of course, if you have a more complex situation, with multiple decorators, or decorators defining multiple methods, this may not work for you.
But I think it is better than stubbing the find. You generally don't want to stub your models in an integration test.
One minor change if you want to test the return value of :bar -
test "bar" do
Model.any_instance.expects(:foo).returns("bar")
assert_equal "bar", get(:bar)
end
But if you are just testing that a model instance has the decorator method(s), do you really need to test for that? It seems like you are testing Object#extend in that case.
If you want to test the behavior of #model.foo, you don't need to do that in an integration test - that's the advantage of the decorator, you can then test it in isolation like
x = Object.new.extend(Decorator)
#.... assert something about x.foo ...
Mocking in integration tests is usually a code smell, in my experience.

How to test if module adds a method to before_filter when included by a controller class

I'm wondering how would you solve this task
You want to refactor following code
class AController < ActionController::Base
before_filter :the_method
protected
def the_method
end
end
into
class AController < ActionController::Base
include TheModule
end
but as you are BDD enthusiast, you have to write the spec first
describe TheModule do
context "when included" do
it "adds #the_method as a before_filter" do
# insert your code here
end
end
end
In other words the question is:
How to write a spec that checks if TheModel#the_method is added as a before_filter when included in a class (presumably a controller class).
Well you can check the presence of the method the callback calls as such.
#model.methods.include?('before_callback_method_name')
However this just checks the method the callback runs is present, not that its a specific type.
I cannot find a way to get a list of the callbacks for a model.
Only option would be to test that the model does as its supposed to if the callback was present i.e if the callback reverses a string on update, check the string is reversed when updated.
* EDIT *
You can retrieve a list of callbacks like so :
#u._validate_callbacks
It returns a callback chain , which include there names and importantly , type.
>> u._validate_callbacks.first.class
=> ActiveSupport::Callbacks::Callback
>> u._validate_callbacks.first.kind
=> :before
>> u._validate_callbacks.first.filter
=> :validate_associated_records_for_sites
Should be able to test accurately if its loaded now.
I did it like this:
describe TheModule do
context "when included" do
it "adds #the_method as a before_filter" do
instance = Object.new
instance.class_eval do |klass|
expects(:before_filter).with(:expensive_method)
include TheModule
end
end
end
end
Since we don't need to test the behavior of before filter. We can just test if it was executed properly.

Resources