Testing instance variable assignment in constructor (RSpec) - ruby-on-rails

I would like to test simple constructor:
def initialize(parameters_hash)
#parameters_hash = parameters_hash
end
Do you think that approach:
describe '.new' do
let(:parameters_hash) { {} }
it 'assigns input to #parameters_hash instance variable' do
expect(subject.instance_variable_get('#parameters_hash')).to eq(parameters_hash)
end
end
is proper. Maybe you know some other solutions.

Rspec already implements the assigns helper see here for a controller that does exactly what you need.
If you need something like that for a model, consider using an attr_reader, and test that
expect(subject.parameters_hash).to eq({})

Related

Ruby/Rails testing - access variable outside of scope?

I want to unit test a method with rspec for RoR and have a method like this:
def create_record(obj, params)
begin
obj.add_attributes(params)
result = obj.save
rescue
MyMailer.failed_upload(#other_var, obj.api_class_name, params).deliver_now
end
end
create_record is never invoked directly, but through another method which fills in #other_var appropriately.
How should I go about testing the code to make sure MyMailer is called correctly? Should I have passed #other_var into the method instead of relying on it being filled in elsewhere (aka: is this a code smell?)? Thanks!
In Ruby you can use Object#instance_variable_set to set any instance variable.
RSpec.describe Thing do
describe "#create_record" do
let(:thing) do
t = Thing.new
t.instance_variable_set(:#other_var, "foo")
t
end
# ...
end
end
This completely circumvents any encapsulation which means that the use of instance_variable_set can be considered a code smell.
Another alternative is to use RSpecs mocking and stubbing facilities but stubbing the actual object under test is also a code smell.
You can avoid this by passing the dependency as a parameter or by constructor injection:
class Thing
attr_accessor :other_var
def initialize(other_var: nil)
#other_var = other_var
end
def create_record(obj, attributes)
# ...
end
end
A good pattern for this is service objects.

Rspec allow_any_instance_of to return instance id

Is it possible to do something like this???
allow_any_instance_of(Object).to receive(:foo).and_return("hello #{instance.id}")
Can i return a message depending on the instance?
Yes, using the "block" form of the matcher, which gives you access to the instance as the formal parameter to the block. You also need to make sure that Object (or whatever class you are passing to allow...) implements :foo (or whatever method you are specifying) as an instance method, or the allow... will raise an error. Similarly, of course, you need to make sure that id is implemented as well.
Here is some sample code using Object itself:
class Object
def id
'bar'
end
def foo
end
end
describe '' do
it '' do
allow_any_instance_of(Object).to receive(:foo) { |o| "hello #{o.id}" }
puts Object.new.foo
end
end
Use a gem like factory_girl or fabrication to generate test objects.
That will let you do...
this_object = Fabricate(:object)
expect(this_object).to receive(:foo).and_return("hello #{this_object.id}")

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 can I stub find_each for rspec testing in rails 3

I was wondering how to test a find_each call in rspec. I'm used to simply stubbing what I want my models to return so I don't rely on test data in the db like this:
MyClass.stub(:find).and_return(my_mock)
However, in another class I'm doing this:
MyClass.find_each do |instance_of_my_class|
do_stuff_here_on(instance_of_my_class)
end
I find that if I do this:
MyClass.stub(:find_each).and_return([one_mock, two_mock])
in the spec test, the "do stuff here" part is not being executed. Does anyone know how to stub a find_each for rspec testing?
You can use and_yield to make rspec call the block passed to the mock:
MyClass.stub(:find_each).and_yield(one_mock).and_yield(two_mock)
If you need to stub find_each on a verified double and have it loop through a specific array of values, you can do this:
let(:my_relation_with_mocked_find_each) do
relation = instance_double('YourModel::ActiveRecord_Relation')
receive_yield = receive(:find_each)
fake_objs.each do |obj|
receive_yield = receive_yield.and_yield(obj)
end
allow(relation).to receive_yield
relation
end
The whole point of stubbing a method is so that the method returns an expected value and not execute its contents. If you have a bunch of logic within the find_each method, I would recommend moving it to a separate method and testing that logic separately. You can then test that your method is called during execution.
Here's a pretty high level example:
class Example1
def my_method
# some logic
end
end
class Example2
def my_other_method
Example1.find_each(&:my_method)
end
end
Rspec:
describe Example1 do
it "should return something" do
example = Example1.new
example.my_method.should == something
end
end
describe Example2 do
it "should call my_method on Example1" do
example1 = mock(:example1, :my_method => true)
example2 = Example2.new
example1.should_receive(:my_method)
example2.my_other_method
end
end
This should do it:
MyClass.stub(:find_each) {|block|
block.call
[one_mock, two_mock]
}
If do_stuff_here_on isn't globally reachable, e.g. an instance method on some_object, you'll need some instance_eval to get the right scope for the block:
MyClass.stub(:find_each) {|block|
some_object.instance_eval(&block)
[one_mock, two_mock]
}

How can I stub a before_filter in a super class in Rails?

I'm using RR for mocking and stubbing in RSpec, and I've run across a situation where I'd like to stub a method from a super class of a controller that sets some instance variables. I can work out how to stub the method call and if I debug I can see that my stubbed block is called, but I cannot get the instance variables in the block to propagate into the class I'm testing.
Just to break it down :
class A < ApplicationController
before_filter :bogglesnap
def bogglesnap
#instancevar = "totally boggled"
end
end
class B < A
def do_something_with_instance
if #instancevar
....
else
....
end
end
end
That's the basic setup, and so then in my tests for controller B I'd like to stub out the bogglesnap method from A to set #instancevar to something I want. I just can't figure out how to do it.
I've tried RR's instance_of stubbing and just stubbing out the controller definition :
stub.instance_of(A).bogglensap { #instancevar = "known value" }
stub(controller).bogglesnap { #instancevar = "known value" }
but neither of these seem to work, well, they don't work :)
Does anyone have any pointers on how you should be able to stub that method call out and have it set instance variables? I'm assuming it has to do with the context in which the block is run but am hoping someone has run across something like this before.
Thanks
You can use instance_variable_set method by calling on the object instance and set it to whatever you want, like so
controller.instance_variable_set("#instancevar", "known value")
and similarly, if you ever want to fetch the value of an instance variable in your spec or debug or do something else from outside the class then you can get the value by doing
controller.instance_variable_get("#instancevar")
Mind you, instance_variable_set and instance_variable_get methods are available not only to controllers but all objects as it is provided by ruby. Infact, these two methods play an important role in rails magic :)

Resources