Rails/Rspec: How to Test #Initialize Method

Rails / RSpec: How to test #initialize method?

For me, expectations are about designing conversations among collaborators. So, you have to decide - is #pick_seed an internal implementation detail or part of a collaborating role's interface?

If pick_seed is an implementation detail, an expectation is the wrong tool for the job. And, since you have an accessor for seed, you can proceed thusly (notice the one-assertion-per-example):

class Generator
attr_accessor :seed

def initialize(seed = nil)
@seed = seed || pick_seed
end

def pick_seed
Time.now.to_i
end
end

describe Generator do
context "creating" do
context "when a seed is specified" do
it "uses that seed" do
seed = 123
generator = Generator.new(seed)
generator.seed.should == seed
end
end
context "when a seed is not specified" do
it "creates its own seed" do
generator = Generator.new
generator.seed.should_not be_nil
end
end
end
end

OTOH, if picking the seed is part of the "seed picker" role, then mocks are valuable in designing the seed picker, and dependency injection is a standard method of assigning the roles. You could write something like:

class GeneratorWithCollaborator
attr_accessor :seed

def initialize(seed = nil, seed_picker = self)
@seed = seed || seed_picker.pick_seed
end

def pick_seed
Time.now.to_i
end
end

describe GeneratorWithCollaborator do
context "creating" do
context "when a seed is specified" do
it "uses that seed" do
seed = 123
seed_picker = double('seed picker')
seed_picker.should_not_receive(:pick_seed)
generator = GeneratorWithCollaborator.new(seed, seed_picker)
generator.pick_seed
end
end

context "when a seed is not specified" do
it "delegates to its seed picker" do
seed_picker = double('seed picker')
seed_picker.should_receive(:pick_seed)
generator = GeneratorWithCollaborator.new(nil, seed_picker)
end
end
end
end

RSpec: How to test init methods

You are searching for it:

expect_any_instance_of(Journey).to receive(:start_journey)

https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class

How to test initialize method of ruby which inturn calls the private method

I suggest rewriting it a bit with memorization.

module Plugin
module Rules
class RulesLoader
include Singleton

def fetch_rule_definition_for(key)
@definitions ||= load_definitions
@definitions[key]
end

private

def load_definitions
#Other code
hash = {}
#this method returns the hash after processing
end
end
end
end

A hypothetical spec:

describe Plugin::Rules::RulesLoader do
context '#fetch_rule_definition_for' do
subject(:fetch_rule_definition) { described_class.instance.fetch_rule_definition_for(key) }

context 'with valid key' do
let(:key) { :valid_key }

it 'returns definitions' do
expect(fetch_rule_definition).to eq(#YOUR_VALUE_HERE#)
end
end

context 'with invalid key' do
let(:key) { :invalid_key }

it 'returns nil' do
expect(fetch_rule_definition).to be_nil
end
end
end
end

How do I test with Rspec an initialize method that invokes find_or_initialize_by?

You could use

expect(document).to receive_message_chain(:template_variables, :find_or_initialize_by).exactly(8).times

but it's a mess and you would have to also check if each call got proper parameters.

I would suggest extracting this to a method:
Document#initialize_variables(template_variables)

then you could test it as simply as

expect(document).to receive(:initialize_variables).with(expected_hash)

and then you can cover Document#initialize_variables with specs and test it's behavior in depth.

RSpec - how to test if object sends messages to self in #initialize

Spies are an alternate type of test double that support this pattern
by allowing you to expect that a message has been received after the
fact, using have_received.

https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/basics/spies

Only spy object can store the method calls. To test your real class in the way that you want, you have to use expect_any_instance_of statement before the class will be initialized:

expect_any_instance_of(Picture).to receive(:add_single_pics)
Picture.new('Test Picture', 'Test-Picture')

In this case your add_single_pics method will be called, but its logic will not be run, if you need to run it you need to call the and_call_original method on the matcher:

expect_any_instance_of(Picture).to receive(:add_single_pics).and_call_original

How to test initializer number of arguments in Ruby

The problem is that Card.new is defined in Card.initialize.

So:

it { expect(Card).to respond_to(:new).with(2).arguments }

is actually trying to test:

class Card
def self.new(number, suit)
...
end
end

You could simply try to initialize Card with incorrect arguments and check that ArgumentError is raised:

class Card
attr_reader :number, :suit

def initialize(number, suit)
@number = number
@suit = suit
end
end

describe Card do
context 'when initialized with incorrect number of arguments' do
[[], [1], [1, 2, 3], [1, 2, 3, 4]].each do |args|
it { expect { Card.new(*args) }.to raise_error(ArgumentError) }
end
end

context 'when initialized with correct arguments' do
it { expect(Card.new(1, 'D')).to be_a(Card) }
end
end

It outputs:

Card
when initialized with incorrect number of arguments
should raise ArgumentError
should raise ArgumentError
should raise ArgumentError
should raise ArgumentError
when initialized with correct arguments
should be a kind of Card

Finished in 0.01455 seconds (files took 0.64291 seconds to load)
5 examples, 0 failures

There might be a cleaner way, though.

RSpec and Object Initialization in Ruby

I. I was able to recreate your issue with a code like this

class Manager
def self.run(a, b)
new(a, b)
end
end

RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end

RSpec.describe Manager, type: :api do
let(:a) { 1 }
let(:b) { 2 }

describe '::run' do
it 'instantiates the class with 2 arguments' do
expect(Manager).to receive(:new).with(a, b)
Manager.run(a, b)
end
end
end

Which results in:

1) Manager#run instantiates the class with 2 arguments
Failure/Error: expect(Manager).to receive(:new).with(a, b)
Wrong number of arguments. Expected 0, got 2.

This happens because of the verifying functionality. When this setting is enabled (and it should be), RSpec will make sure the the object implements the interface that is being stubbed/mocked. In this case RSpec throws an error on the line expect(Manager).to receive(:new).with(a, b), because it actually looks into the Manager class and checks whether initialize can take 2 arguments.

If you change the manager to look like this the example will pass:

class Manager
attr_reader :a, :b

def initialize(a, b)
@a = a
@b = b
end

def self.run(a, b)
new(a, b)
end
end

II. But you don't really need to use mocks for functionality like this. If you are just checking whether the right kind of instance is returned it is better to just look at the real thing.

RSpec.describe Manager, type: :api do
let(:a) { 1 }
let(:b) { 2 }

describe '::run' do
subject { described_class.run(a, b) }

it 'instantiates the class with 2 arguments' do
expect(subject).to be_an_instance_of(Manager)
end

it 'sets a to the first argument' do
expect(subject.a).to eq(a)
end

it 'sets b to the second argument' do
expect(subject.b).to eq(b)
end
end
end

III. In this example:

expect(Manager).to receive(:run).with(first_arg, second_arg)
Manager.run(first_arg, second_arg)

You set up an assertion, and then immediately called the code to pass that assertion. So weren't really testing anything.

Mocking/stubbing correctly is fairly advanced testing concept, and it is easy to get it wrong so if you can go without it, just go without, it will make things easier.

If you want to learn more about what to test/when to mock. I recommend this talk by Sandi Metz. https://www.youtube.com/watch?v=URSWYvyc42M



Related Topics



Leave a reply



Submit