RSpec class_spy on Rails Mailer - ruby-on-rails

I am trying to test that a specific mailer class is used when a model is saved. In my model I have:
class Foo < ActiveRecord::Base
def send_email
if some_condition
FooMailer.welcome.deliver_now
else
FooBarMailer.welcome.deliver_now
end
end
def
In my tests for Foo class I have the following
it 'uses the foo bar mailer' do
foo_mailer = class_spy(FooMailer)
subject.send_email
# some_condition will evaluate to false here, so we'll use the FooMailer
expect(foo_mailer).to have_received :welcome
end
When I run this test it fails with:
(ClassDouble(FooMailer) (anonymous)).welcome(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments

The issue would seem to be that you haven't swapped out the current definition of your mailer class with the spy, so your spy isn't receiving any messages. To replace it, you would use the stub_const method:
it 'uses the foo bar mailer' do
foobar_mailer = class_spy(FooBarMailer)
stub_const('FooBarMailer', foobar_mailer)
subject.send_email
# some_condition will evaluate to false here, so we'll use the FooBarMailer
expect(foobar_mailer).to have_received :welcome
end

This is a syntax sugar for the accepted answer.
foo_mailer = class_spy(FooMailer).as_stubbed_const

Related

Rspec "NoMethodError" from nested module

I'm running into a weird error:
Class:
module AnimalSanctuary
module AnimalInspector
class AnimalPicker
def initialize(number_of_animals, ids)
#number_of_animals = number_of_animals
#ids = ids
end
...
def pick_animals(animal)
end
end
end
test:
require 'rails_helper'
RSpec.describe AnimalSanctuary::AnimalInspector::AnimalPicker do
describe ".pick_animals" do
context "pick an available animal" do
it "returns animal name" do
expect(AnimalSanctuary::AnimalInspector::AnimalPicker.pick_animals("Dog")).to eq("Dog")
end
end
end
end
I get the following error:
NoMethodError:
undefined method `pick_animals' for AnimalSanctuary::AnimalInspector::AnimalPicker:Class
Rspec calls the class but not the method which has stumped me. Am I doing something wrong?
The definition of pick_animals is an instance method.
To call it, you will need to instantiate an object of the class using the new method as shown below. I have passed in random values to your initializer (1, [1,2]) however you can set them as you like.:
number_of_animals = 1
ids = [1,2]
AnimalSanctuary::AnimalInspector::AnimalPicker.new(number_of_animals, ids).pick_animals("Dog")
Otherwise, to call it the way you are calling it, you will need to redefine it as a class method by using self.pick_animals as shown below:
module AnimalSanctuary
module AnimalInspector
class AnimalPicker
...
def self.pick_animals(animal)
end
end
end
yeah pick_animals is an instance method.
you can use the following in your rspec
expect_any_instance_of(nimalSanctuary::AnimalInspector::AnimalPicker).to receive(:pick_animals).with("dogs").to_eq("Dog")
Hope this helps

RSpec: How to properly test class method that loops through a collection / calls instance method

Consider the following class and class method:
class Foo < ActiveRecord::Base
scope :active, -> { where(deleted: false) }
class << self
def some_class_method
active.each do |foo|
foo.some_instance_method
end
end
end
end
what is the best practice to test such a method in RSpec? What I have learned thus far suggests that I should make sure that each active instance of Foo receives a call to some_instance_method, but if I were to make an expectation regarding Foo.some_class_method, to my knowledge I cannot assert a nested expectation about any instance of Foo.
Any help would be appreciated!!
The approach I would take is to separately test some_instance_method, like:
it 'should return some value' do
expect(foo.some_instance_method).to eq('some value')
end
You could then run the class method and test that the transformations happened as expected:
context 'Foo#some_class_method' do
it 'should have some effect' do
expect(some_comparison_variable).to eq('some_before_state')
Foo.some_class_method
expect(some_comparison_variable).to eq('some_after_state')
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.

Rspec - Module inside class

I have tried several way to rspec the 'to_type' function. The fact that it is inside the class means that only the class should be able to call it right? I've tried to include the Class in my rspec but the module "Format" is still not recognized. Any ideas how I can rspec this method 'to_type' from the module?
class Loom::Lma < Loom::Base
module Format
STANDARD_FORMATS = {
1 => '0',
2 => '13.4',
}
def to_type(format)
# type is calculated here then return type
# for instance
return :date
end
module_function :to_type
end
def initialize()
#init stuff
end
def otherstuff()
#another function
end
end
RSPEC
it 'type should not be :date' do
include Loom::Lma
Format.to_type('some string format').should_not eq(:date)
end
Any ideas?
Are you sure you want to put that module into a class not the other way around?
Anyway, you can access to_type like this:
Loom::Lma::Format.to_type()

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.

Resources