Testing if some function call method on associated object in Rspec - ruby

Assume I have some method which call another method on some object:
def initialize
#obj = SomeClass.new
end
def method
#obj.another_method
end
How can I test this with Rspec and .should_receive?

You can do it by passing obj to your class. This technique is called Dependency Injection
http://sporto.github.io/blog/2013/09/25/simple-dependency-injection/
require "rspec"
class Foo
def initialize(obj = SomeClass.new)
#obj = obj
end
def method
#obj.another_method
end
end
describe Foo do
describe "#method" do
subject { Foo.new(obj) }
let(:obj){ mock }
it "delegates to another_method" do
obj.should_receive(:another_method).and_return("correct result")
subject.method.should eq "correct result"
end
end
end
You can also do it like this but it's very bad way of testing class internals
require "rspec"
class Foo
def initialize
#obj = SomeClass.new
end
def method
#obj.another_method
end
end
describe Foo do
describe "#method" do
it "delegates to another_method" do
subject.instance_variable_get(:#obj).should_receive(:another_method).and_return("correct result")
subject.method.should eq "correct result"
end
end
describe "#method" do
it "delegates to another_method" do
SomeClass.stub_chain(:new, :another_method).and_return("correct result")
subject.method.should eq "correct result"
end
end
describe "#method" do
let(:obj) { mock(another_method: "correct result") }
it "delegates to another_method" do
SomeClass.stub(:new).and_return(obj)
obj.should_receive(:another_method)
subject.method.should eq "correct result"
end
end
end
In my code I would use depedency injection and only test output which means no #should_receive at all
require "rspec"
class Foo
attr_reader :obj
def initialize(obj = Object.new)
#obj = obj
end
def method
obj.another_method
end
end
describe Foo do
describe "#method" do
subject { Foo.new(obj)}
let(:obj){ mock }
it "delegates to another_method" do
obj.stub(:another_method).and_return("correct result")
subject.method.should eq "correct result"
end
end
end

While the dependency injection provided by the other answer is preferable, given your existing code, you would need to do something like:
describe "your class's method" do
it "should invoke another method" do
some_mock = double('SomeClass')
SomeClass.should_receive(:new).and_return(some_mock)
someMock.should_receive(:another_method).and_return('dummy_value')
expect(YourClass.new.another_method).to eq('dummy_value')
end
end
where YourClass is the class in question.
Update: Added check for returned value with nod to #Lewy

Related

RSpec: How to mock an object and methods that take parameters

I'm writing RSpec unit tests for a CommandLineInterface class that I've created for my Directory object. The CommandLineInterface class uses this Directory object to print out a list of people in my Directory. Directory has a #sort_by(param) method that returns an array of strings. The order of the strings depends on the param passed to the #sort_by method (e.g., sort_by("gender"). What would be the correct way to mock out this Directory behavior in my CLI specs? Would I use an instance_double? I am not sure how to do this for a method that takes parameters, like sorting by gender.
I'm only using Ruby and RSpec. No Rails, ActiveRecord, etc. being used here.
Snippets from the class and method I want to mock out:
class Directory
def initialize(params)
#
end
def sort_by(param)
case param
when "gender" then #people.sort_by(&:gender)
when "name" then #people.sort_by(&:name)
else raise ArgumentError
end
end
end
It all depends on how your objects are collaborating.
Some information is lacking in your question:
How does CommandLineInterface use Directory? Does it create an instance by itself or does it receive one as an argument?
Are you testing class methods or instance methods? (Prefer instance methods)
Here's how you could do it if you pass in the dependent object:
require 'rspec/autorun'
class A
def initialize(b)
#b = b
end
def foo(thing)
#b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:b) { double('an instance of B') }
let(:a) { A.new(b) }
it 'calls b.bar with qux' do
expect(b).to receive(:bar).with('qux')
a.foo('qux')
end
end
end
end
If the class initializes the dependant object and it isn't important to know which instance got the message you can do this:
require 'rspec/autorun'
B = Class.new
class A
def initialize
#b = B.new
end
def foo(thing)
#b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:a) { A.new }
it 'calls b.bar with qux' do
expect_any_instance_of(B).to receive(:bar).with('qux')
a.foo('qux')
end
end
end
end
If you just want to stub out the return value and not test whether the exact message was received, you can use allow:
require 'rspec/autorun'
B = Class.new
class A
def initialize
#b = B.new
end
def foo(thing)
thing + #b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:a) { A.new }
it 'returns qux and b.bar' do
allow_any_instance_of(B).to receive(:bar).with('qux') { 'jabber' }
expect(a.foo('qux')).to eq('quxjabber')
end
end
end
end

Rspec: How to properly test Template Method pattern?

Given two classes that use the template method design pattern:
def Parent
def all_params
params.merge(extra_params)
end
def params
{blah: "cool"}
end
def extra_params
{}
end
end
def Child < Parent
def extra_params
{edmund: "coolest"}
end
end
What is the proper way to test this in Rspec? Should I be creating shared_examples_for "a parent" like this and then testing using it_should_behave_like 'a parent' in both classes?:
shared_examples_for "a parent" do
describe "#params" do
expect(subject.params).to eq (...)
end
describe "#all_params" do
expected_params = subject.all_params.merge(subject.extra_params)
expect(subject.all_params).to eq expected_params
end
end
describe Parent do
subject { Parent.new }
it_should_behave_like 'a parent'
describe "#extra_params" do
expect(subject.extra_params).to eq {}
end
end
describe Child do
subject { Child.new }
it_should_behave_like 'a parent'
describe "#extra_params" do
expect(subject.extra_params).to eq {edmund: 'coolest'}
end
end
Or should I be testing that Child is a parent, and only testing the hook methods that it overwrites?
describe Parent do
subject { Parent.new }
describe "#params" do
expect(subject.params).to eq (...)
end
describe "#all_params" do
expected_params = subject.all_params.merge(subject.extra_params)
expect(subject.all_params).to eq expected_params
end
describe "#extra_params" do
expect(subject.extra_params).to eq {}
end
end
describe Child do
subject { Child.new }
it "is a Parent" do
expect(subject).to_be kind_of(Parent)
end
describe "#extra_params" do
expect(subject.extra_params).to eq {edmund: 'coolest'}
end
end
It's a matter of choice. Personally I create shared examples for the parent class, then for the parent e.g. Animal
include_examples 'Animal'
and for the e.g. Badger < Animal
it_behaves_like 'Animal'
First one includes the example to the current context, while the second one wraps the example in a behaves like Animal context.
I prefer the second example. Using it_behaves_like "a parent" in Child is duplicative, and is essentially just testing whether inheritance works in Ruby.

What's best practice for mocking new method on different class

I have next scenario:
module Module
class CommandPattern
def initialize(value)
command = []
#var = value['something']
#abc = value['abc']
#command << value
end
def add(value)
#command << value
end
def get_command
#command
end
end
end
module Module
class Implementator
def initialize(value)
#value = value
end
def method_to_test(argument)
var = "command1"
cmd = CommandPattern.new(var)
var2 = "command2"
cmd.add(var2)
var3 = argument
cmd.add(var3)
commands = var + var2 + var3
commands
end
end
end
So, when I'm testing Module::B.method_I_want_to_test, what would be the best practice to mock "var = A.new(some_stuff)"? Beside refactoring and moving this line into separate method, is there some nice way to do this?
Little bit of background on this question - this style (Module::ClassA and Module::ClassB) - I'm using http://naildrivin5.com/gli/ and reason for this approach is that class A is actually implementing Command Pattern.
So issue I was apparently getting was due to wrong way of trying to write specs.
What I did before was (on the way how #spickermann advised):
RSpec.describe Module::Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
Issue was apparently in testing Module::Implementator, didn't realise I can put module around my RSpec.describe block and solve my first issue:
module Module
RSpec.describe Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
end
Another issue I had was global variable keeping YAML structure, which I missed to see and declare in spec_helper.rb
However, thank's to #spickermann's advices, issue is solved.
I would start with something like this:
describe '#method_I_want_to_test' do
let(:something) { # whatever something needs to be }
let(:a) { double(A, # methods you need from a) }
subject(:method_I_want_to_test) do
B.new(something).method_I_want_to_test
end
before do
allow(A).to receive(:new).with(something).and_return(a)
end
it 'does what I expect' do
expect(method_I_want_to_test).to eq(# what do_semething_else returns)
end
end
The interesting part is the before block that stubs the new method on A. It returns always the double defined in the let(:a) line instead of a real instance of A

Is there a way to stub a method of an included module with Rspec?

I have a module that is included in another module, and they both implement the same method.
I would like to stub the method of the included module, something like this:
module M
def foo
:M
end
end
module A
class << self
include M
def foo
super
end
end
end
describe "trying to stub the included method" do
before { allow(M).to receive(:foo).and_return(:bar) }
it "should be stubbed when calling M" do
expect(M.foo).to eq :bar
end
it "should be stubbed when calling A" do
expect(A.foo).to eq :bar
end
end
The first test is passing, but the second one outputs:
Failure/Error: expect(A.foo).to eq :bar
expected: :bar
got: :M
Why isn't the stub working in this case?
Is there a different way to achieve this?
Thanks!
-------------------------------------UPDATE----------------------------------
Thanks! using allow_any_instance_of(M) solved this one.
My next question is - what happens if I use prepend and not include? see the following code:
module M
def foo
super
end
end
module A
class << self
prepend M
def foo
:A
end
end
end
describe "trying to stub the included method" do
before { allow_any_instance_of(M).to receive(:foo).and_return(:bar) }
it "should be stubbed when calling A" do
expect(A.foo).to eq :bar
end
end
This time, using allow_any_instance_of(M) results in an infinite loop. why is that?
Note you cannot directly call M.foo! Your code only seems to work because you mocked M.foo to return :bar.
When you open A metaclass (class << self) to include M, you have to mock any instance of M, that is adding to your before block:
allow_any_instance_of(M).to receive(:foo).and_return(:bar)
module M
def foo
:M
end
end
module A
class << self
include M
def foo
super
end
end
end
describe "trying to stub the included method" do
before do
allow(M).to receive(:foo).and_return(:bar)
allow_any_instance_of(M).to receive(:foo).and_return(:bar)
end
it "should be stubbed when calling M" do
expect(M.foo).to eq :bar
end
it "should be stubbed when calling A" do
expect(A.foo).to eq :bar
end
end

how to access instant variable with rspec

I have a class like this.
require 'net/http'
class Foo
def initialize
#error_count = 0
end
def run
result = Net::HTTP.start("google.com")
#error_count = 0 if result
rescue
#error_count += 1
end
end
And I want to count up #error_count if connection fails, so I wrote like this.
require_relative 'foo'
describe Foo do
before(:each){#foo = Foo.new}
describe "#run" do
context "when connection fails" do
before(:each){ Net::HTTP.stub(:start).and_raise }
it "should count up #error_count" do
expect{ #foo.run }.to change{ #foo.error_count }.from(0).to(1)
end
end
end
end
Then I got this error.
NoMethodError:
undefined method `error_count' for #<Foo:0x007fc8e20dcbd8 #error_count=0
How can I access instance variable with Rspec?
Edit
describe Foo do
let(:foo){ Foo.new}
describe "#run" do
context "when connection fails" do
before(:each){ Net::HTTP.stub(:start).and_raise }
it "should count up #error_count" do
expect{ foo.run }.to change{foo.send(:error_count)}.from(0).to(1)
end
end
end
end
Try #foo.send(:error_count) i guess it should work.
Update: found in docs
expect{ foo.run }.to change{foo.instance_variable_get(:#error_count)}.from(0).to(1)

Resources