Rspec: How to properly test Template Method pattern? - ruby

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.

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

how to test ruby inheritance with rspec

I am trying to test logic that runs during class inheritance, but have run into an issue when running multiple assertions.
i first tried...
describe 'self.inherited' do
before do
class Foo
def self.inherited klass; end
end
Foo.stub(:inherited)
class Bar < Foo; end
end
it 'should call self.inherited' do
# this fails if it doesn't run first
expect(Foo).to have_received(:inherited).with Bar
end
it 'should do something else' do
expect(true).to eq true
end
end
but this fails because the Bar class has already been loaded, and therefore does not call inherited a 2nd time. If the assertion doesn't run first... it fails.
So then i tried something like...
describe 'self.inherited once' do
before do
class Foo
def self.inherited klass; end
end
Foo.stub(:inherited)
class Bar < Foo; end
end
it 'should call self.inherited' do
#tested ||= false
unless #tested
expect(Foo).to have_receive(:inherited).with Bar
#tested = true
end
end
it 'should do something else' do
expect(true).to eq true
end
end
because #tested doesn't persist from test to test, the test doesn't just run once.
anyone have any clever ways to accomplish this? This is a contrived example and i dont actually need to test ruby itself ;)
Here's an easy way to test for class inheritance with RSpec:
Given
class A < B; end
a much simpler way to test inheritance with RSpec would be:
describe A do
it { expect(described_class).to be < B }
end
For something like this
class Child < Parent; end
I usually do:
it 'should inherit behavior from Parent' do
expect(Child.superclass).to eq(Parent)
end
For some reason, I did not manage the solution from David Posey to work (I guess I did something wrong. Feel free to provide a solution in the comments). In case someone out there have the same problem, this works too:
describe A
it { expect(described_class.superclass).to be B }
end
Make your class definition for testing inheritance run during the test:
describe 'self.inherited' do
before do
class Foo
def self.inherited klass; end
end
# For testing other properties of subclasses
class Baz < Foo; end
end
it 'should call self.inherited' do
Foo.stub(:inherited)
class Bar < Foo; end
expect(Foo).to have_received(:inherited).with Bar
end
it 'should do something else' do
expect(true).to eq true
end
end
Another way:
class Foo; end
class Bar < Foo; end
class Baz; end
RSpec.describe do
it 'is inherited' do
expect(Bar < Foo).to eq true
end
it 'is not inherited' do
expect(Baz < Foo).not_to eq true
end
end

RSpec Matchers for function arguments

I'm new to writing custom matchers, and most of the examples cover a very minimal set up. What's the proper way to write a matcher that extends a function from a module that has an argument. Do I need to give the actual block the function argument input? Thanks.
# My Example:
RSpec::Matchers.define :total do |expected|
match do |input, actual|
actual.extend(Statistics).sample(input) == expected
end
end
# Before:
describe Statistics do
it 'should not be empty' do
expect(Statistics.sample(input)).not_to be_empty
end
end
Well it depends on what you want to test. If you merely want to test that the module includes a method, maybe something like this:
module Statistics
def sample
end
end
class Test
end
RSpec::Matchers.define :extend_with do |method_name|
match do |klass|
klass.extend(Statistics).respond_to?(method_name)
end
end
describe Statistics do
subject { Test.new }
it { should extend_with(:sample) }
end
If you want to test the value returned, you can add that as an argument, or chain the matcher:
module Statistics
def sample(input)
41 + input
end
end
class Test
end
RSpec::Matchers.define :extend_with do |method_name, input|
match do |klass|
#klass = klass
#klass.extend(Statistics).respond_to?(method_name)
end
chain :returning_value do |value|
#klass.extend(Statistics).__send__(method_name, input) == value
end
end
describe Statistics do
subject { Test.new }
it { should extend_with(:sample) }
it { should extend_with(:sample, 2).returning_value(43) }
end
The matcher DSL is quite flexible. You don't have to be hung up on naming your arguments 'actual' and 'expected' like in the docs -- write the specs so they tell the story of your code.

Testing if some function call method on associated object in Rspec

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

Ruby Metaprogramming: Check if called by parent class?

Given the following classes
class Parent
def hello
puts "I am the parent class"
end
def call_parent_hello
hello
end
end
class Child < Parent
def hello
puts "I am the child class"
end
end
When I do the following:
c = Child.new
c.hello # => Outputs: "I am the child class"
c.call_parent_hello # => Outputs: "I am the child class"
Is it possible to make Child#call_parent_hello access the Parent#hello, but without altering the Parent class?
I am looking for some kind of called_by_parent_class? implementation like this:
def hello
if called_by_parent_class?
super
else
puts "I am the child class"
end
end
You can use the super keyword:
class Child < Parent
def hello
super
end
end
I think you're looking to do something like this:
class Parent
def hello( opts = '' )
"Who's talking? The #{self.class} class is via the Parent class!"
end
end
class Child < Parent
def hello( opts = '' )
if opts == 'super'
super
else
"I have a parent and an independent voice"
end
end
def call_mom
hello( 'super' )
end
end
c1 = Child.new
puts c1.hello => "I have a parent and an independent voice"
puts c1.call_mom => "Who's talking? The Child class is via the Parent class!"
However (and I'm not trolling here) I also think you're kind of missing the point of subclassing. Generally you would subclass to get this automatic scoping of methods. If you break out of that I think you would want to instantiate an instance of Parent. But to each his own.
Good luck!
After rereading your question I see your real question is:
Is it possible to make Child#call_parent_hello access the Parent#hello, but without altering the Parent class?
Changing your child class to be:
class Child < Parent
alias_method :call_parent_hello, :hello
def hello
puts "I am the child class"
end
end
solves the problem just as you ask
Use super. super calls the same method in the parent class
class Child < Parent
def call_parent_hello
super
end
end
Use direct hierarchy call. Class#ancestors gives you the hierarchy of the inheritance.
class Child < Parent
def call_parent_hello
self.class.ancestors[1].new.hello
end
end

Resources