How do I verify the number of elements and content of an array using ParameterMatchers? - ruby

I am learning Ruby with TDD using Mocha and MiniTest.
I have a class that has one public method and many private methods, so the only method my tests are going to tests are the public one.
This public method does some processing and creates an array which is sent to another object:
def generate_pairs()
# prepare things
pairs = calculate_pairs()
OutputGenerator.write_to_file(path, pairs)
end
Great. To test it, I would like to mock the OutputGenerator.write_to_file(path, pairs) method and verify the parameters. My first test I could sucessfully implement:
def test_find_pair_for_participant_empty_participant
available_participants = []
OutputGenerator.expects(:write_to_file).once.with('pairs.csv', [])
InputParser.stubs(:parse).once.returns(available_participants)
pair = #pairGenerator.generate_pairs
end
Now I would like to test with one pair of participants.
I am trying this
def test_find_pair_for_participant_only_one_pair
participant = Object.new
participant.stubs(:name).returns("Participant")
participant.stubs(:dept).returns("D1")
participant_one = Object.new
participant_one.stubs(:name).returns("P2")
participant_one.stubs(:dept).returns("D2")
available_participants = [participant_one]
OutputGenerator.expects(:write_to_file).once.with('pairs.csv', equals([Pair.new(participant, participant_one)])) # here it fails, of course
InputParser.stubs(:parse).once.returns(available_participants)
#obj.stubs(:get_random_participant).returns(participant)
pair = #obj.generate_pairs
end
The problem is that equals will only match the obj reference, not the content.
Is there any way I can verify the content of the array? Verifying the number of elements inside the array would also be extremely useful.
ps: I am sorry if the code doesn't follow ruby standards, I am doing this project to learn the language.

What you are testing here demonstrates a kind of hard coupling. That is your primary class is always dependent on OutputGenerator which makes testing your outputs tricky and can lead to a lot of pain if/when you have to refactor your designs.
A good pattern for this is dependency injection. With this you can just write a temporary ruby object you can use to evalute the output of your function however you want:
# in your main class...
class PairGenerator
def initialize(opts={})
#generator = opts[:generator] || OutputGenerator
end
def generate_pairs()
# prepare things
pairs = calculate_pairs()
#generator.write_to_file(path, pairs)
end
end
# in the test file...
# mock class to be used later, this can be at the bottom of the
# test file but here I'm putting it above so you are already
# aware of what it is doing
#
class MockGenerator
attr_reader :path, :pairs
def write_to_file(path, pairs)
#path = path
#pairs = pairs
end
end
def test_find_pair_for_participant_only_one_pair
participant = Object.new
participant.stubs(:name).returns("Participant")
participant.stubs(:dept).returns("D1")
participant_one = Object.new
participant_one.stubs(:name).returns("P2")
participant_one.stubs(:dept).returns("D2")
available_participants = [participant_one]
# set up a mock generator
mock_generator = MockGenerator.new
# feed the mock to a new PairGenerator object as a dependency
pair_generator = PairGenerator.new(generator: mock_generator)
# assuming this is needed from your example
pair_generator.stubs(:get_random_participant).returns(participant)
# execute the code
pair_generator.generator_pairs
# output state is now captured in the mock, you can evaluate for
# all the test cases you care about
assert_equal 2, mock_generator.pairs.length
assert mock_generator.pairs.include?(participant)
end
Hope this helps! Dependency Injection is not always appropriate but it is great for cases like this.
Some other posts about the use of dependency injection you might find helpful:
Dependency Injection in Ruby
A Ruby Refactor: Dependency Injection Options
Simple Dependency Injection in Ruby

Related

How to disable rspec's checking for access to double outside of original example?

A class double is created in a let which is accessed by multiple examples. For example:
let(:foo_klass) do
class_double('A::B::C::Foo')
end
Other unit tests use ObjectSpace.each_object(Class) to examine the classes to select, for example, all modules including another module:
def find_modules_including_module(target_module)
ObjectSpace.each_object(Module).select do |object|
# All Class'es are modules, but we are not interested in them, so we exclude them.
!object.is_a?(Class) \
&& \
object.ancestors.include?(target_module) \
&& \
!object.equal?(target_module)
end
end
It is only the unit tests that call find_modules_including_module that fail. The error looks something like this:
Failure/Error: object.ancestors.include?(target_module) \
#<ClassDouble(MyModule::MyClass) (anonymous)> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.
I understand why failing on any access on the double class is almost always the right thing to do, but mine is a special case in which that behavior is problematic and IMO inappropriate.
Is there an option to suppress this temporarily? Or do you have another suggestion?
In response to #engineersmnky's comments, and to provide context, I am adding the following:
I am doing this whole exercise because I am using the SemanticLogger logging gem, which provides a Loggable module which provides a class method and an instance method named 'logger'. The instance method calls the class method (self.class.logger).
If a module (let's call it M) includes Loggable, and a class (let's call it C) includes M, then C will assimilate M's instance method as if it were its own. When it is called, it will try to get self.class.logger, but self is the C instance and not the module, and since there is no C class method logger, a NoMethodError is raised.
My solution is to add this to every module that includes Loggable:
def self.included(klass)
klass.class_exec do
include SemanticLogger::Loggable
end
end
This works, but there is a risk that in the future a new module will include Loggable but not this included implementation. I want to be able to detect this situation programmatically in a test. My current test looks like this:
specify 'all classes including modules that include Loggable include Loggable themselves' do
modules_including_loggable = modules_including_module(SemanticLogger::Loggable)
classes_including_those_modules = classes_including_modules(modules_including_loggable)
suspicious_classes = classes_not_having_ancestor(classes_including_those_modules, SemanticLogger::Loggable)
expect(suspicious_classes.map(&:name).sort.join("\n")).to eq('')
end
The methods used are defined as follows:
def modules_including_module(target_module)
ObjectSpace.each_object(Module).select do |object|
# All Class'es are modules, but we are not interested in them, so we exclude them.
!object.is_a?(Class) \
&& \
object.ancestors.include?(target_module) \
&& \
!object.equal?(target_module)
end
end
def classes_including_modules(modules)
ObjectSpace.each_object(Class).select do |klass|
(klass.ancestors & modules).any?
end
end
def classes_not_having_ancestor(classes, ancestor)
classes.select do |klass|
!klass.ancestors.include?(ancestor)
end
end

Dry::Web::Container yielding different objects with multiple calls to resolve

I'm trying write a test to assert that all defined operations are called on a successful run. I have the operations for a given process defined in a list and resolve them from a container, like so:
class ProcessController
def call(input)
operations.each { |o| container[o].(input) }
end
def operations
['operation1', 'operation2']
end
def container
My::Container # This is a Dry::Web::Container
end
end
Then I test is as follows:
RSpec.describe ProcessController do
let(:container) { My::Container }
it 'executes all operations' do
subject.operations.each do |op|
expect(container[op]).to receive(:call).and_call_original
end
expect(subject.(input)).to be_success
end
end
This fails because calling container[operation_name] from inside ProcessController and from inside the test yield different instances of the operations. I can verify it by comparing the object ids. Other than that, I know the code is working correctly and all operations are being called.
The container is configured to auto register these operations and has been finalized before the test begins to run.
How do I make resolving the same key return the same item?
TL;DR - https://dry-rb.org/gems/dry-system/test-mode/
Hi, to get the behaviour you're asking for, you'd need to use the memoize option when registering items with your container.
Note that Dry::Web::Container inherits Dry::System::Container, which includes Dry::Container::Mixin, so while the following example is using dry-container, it's still applicable:
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'dry-container'
end
class MyItem; end
class MyContainer
extend Dry::Container::Mixin
register(:item) { MyItem.new }
register(:memoized_item, memoize: true) { MyItem.new }
end
MyContainer[:item].object_id
# => 47171345299860
MyContainer[:item].object_id
# => 47171345290240
MyContainer[:memoized_item].object_id
# => 47171345277260
MyContainer[:memoized_item].object_id
# => 47171345277260
However, to do this from dry-web, you'd need to either memoize all objects auto-registered under the same path, or add the # auto_register: false magic comment to the top of the files that define the dependencies and boot them manually.
Memoizing could cause concurrency issues depending on which app server you're using and whether or not your objects are mutated during the request lifecycle, hence the design of dry-container to not memoize by default.
Another, arguably better option, is to use stubs:
# Extending above code
require 'dry/container/stub'
MyContainer.enable_stubs!
MyContainer.stub(:item, 'Some string')
MyContainer[:item]
# => "Some string"
Side note:
dry-system provides an injector so that you don't need to call the container manually in your objects, so your process controller would become something like:
class ProcessController
include My::Importer['operation1', 'operation2']
def call(input)
[operation1, operation2].each do |operation|
operation.(input)
end
end
end

How to stub class instantiated inside tested class in rspec

I have problem stubbing external api, following is the example
require 'rspec'
require 'google/apis/storage_v1'
module Google
class Storage
def upload file
puts '#' * 90
puts "File #{file} is uploaded to google cloud"
end
end
end
class UploadWorker
include Sidekiq::Worker
def perform
Google::Storage.new.upload 'test.txt'
end
end
RSpec.describe UploadWorker do
it 'uploads to google cloud' do
google_cloud_instance = double(Google::Storage, insert_object: nil)
expect(google_cloud_instance).to receive(:upload)
worker = UploadWorker.new
worker.perform
end
end
I'm trying to stub Google::Storage class. This class is instantiated inside the object being tested. How can I verify the message expectation on this instance?
When I run above example, I get following output, and it seems logical, my double is not used by tested object
(Double Google::Storage).upload(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
I'm new to Rspec and having hard time with this, any help will be appreciated.
Thanks!
Reaching for DI is always a good idea (https://stackoverflow.com/a/51401376/299774) but there are sometimes reasons you can't so it, so here's another way to stub it without changing the "production" code.
1. expect_any_instance_of
it 'uploads to google cloud' do
expect_any_instance_of(Google::Storage).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
In case you just want to test that the method calls the method on any such objects.
2. bit more elaborated setup
In case you want to control or set up more expectations, you can do this
it 'uploads to google cloud' do
the_double = instance_double(Google::Storage)
expect(Google::Storage).to receive(:new).and_return(the_double)
# + optional `.with` in case you wanna assert stuff passed to the constructor
expect(the_double).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
Again - Dependency Injection is clearer, and you should aim for it. This is presented as another possibility.
I would consider reaching for dependency injection, such as:
class UploadWorker
def initialize(dependencies = {})
#storage = dependencies.fetch(:storage) { Google::Storage }
end
def perform
#storage.new.upload 'test.txt'
end
end
Then in the spec you can inject a double:
storage = double
expect(storage).to receive(...) # expection
worker = UploadWorker.new(storage: storage)
worker.perform
If using the initializer is not an option then you could use getter/setter method to inject the dependency:
def storage=(new_storage)
#storage = new_storage
end
def storage
#storage ||= Google::Storage
end
and in the specs:
storage = double
worker.storage = storage

What is a Ruby factory method?

I understand that a factory method is a class method that utilises the self keyword and instantiates an object of it's own class. I don't understand how this is useful or how it can extend the functionality of initialize method.
I'm working on a project creating a command line address book that asks me to use a factory pattern on the Person class so that I can create a Trainee or Instructor (subclasses) with different attributes.
A factory class is a clean way to have a single factory method that produces various kind of objects.
It takes a parameter, a parameter that tells the method which kind of object to create. For example to generate an Employee or a Boss, depending on the symbol that is passed in:
class Person
def initialize(attributes)
end
end
class Boss
def initialize(attributes)
end
end
class Employee
def initialize(attributes)
end
end
class PersonFactory
TYPES = {
employee: Employee,
boss: Boss
}
def self.for(type, attributes)
(TYPES[type] || Person).new(attributes)
end
end
and then:
employee = PersonFactory.for(:employee, name: 'Danny')
boss = PersonFactory.for(:boss, name: 'Danny')
person = PersonFactory.for(:foo, name: 'Danny')
I also wrote a more detailed blog post about that topic: The Factory Pattern
The Factory Method Pattern at least allows you to give an expressive name to what could otherwise be a complicated or opaque constructor. For instance if you have a constructor that takes a bunch of parameters, it may not be clear why to the caller, having a named Factory method or methods could potentially hide the complexity of the object creation and make your code more expressive of what is actually going on.
So in your case a bad design may be:
trainee = Person.new true
or
instructor = Person.new false
Where true or false branches to creating an instructor or trainee.
This could be improved by using a Factory method to clarify what is going on:
trainee = Person.create_trainee
instructor = Person.create_instructor
Why bother with factory methods?
(A) To simplify things:
Creating objects can be complicated, and
you may need to do this multiple times.
It's hard to remember:
# ugh - too much work!
driver = Person.new
engine = Brrrm.new
engine.turbo_charged = true
engine.max_rpm = 100000
car = Porsche.new
car.driver = driver
car.engine = engine
# preference - less to remember
ben = PersonFactory.create("ben")
car = PorscheFactory.create(ben)
# and you get the following for free, without remembering:
car.turbo_charged # => true
car.engine # => brrrm
car.driver # => ben_koshy
car.driver.personality # => :excellent_dude
# you can mix and match default values with options.
# generally speaking you want to inject as much as you can
# i.e. inverting dependencies. I make these illustrates to
# explain a concept, not as an example of great coding.
(B) To allow for overridding / stubbing
If you are writing testable code, you might want to create your own specialised 'crash dummy vehicle' so you can test collisions etc. If you have a factory method / object, then you can do this easily. This is a somewhat adavanced topic - google "creating a seam" or "dependency injection" for more info.

How do I test (TDD) a singleton class?

I am starting with DDD and TDD in a Ruby application using Minitest.
I created a repository class (no database access, but it generates the entities for me). It is a singleton.
I would like to test the generation of the entities. The problem is that because it is a singleton, the order of executions of the tests affect the results.
Is there any way to force the disposal of the singleton element so it is "fresh"?
Here is my repository code:
require "singleton"
class ParticipantRepository
include Singleton
def initialize()
#name_count = 0
end
def generate_participant()
participant = Participant.new
participant.name = "Employee#{get_name_count()}"
return participant
end
private
def get_name_count()
old_name_count = #name_count
#name_count += 1
return old_name_count
end
end
And the tests:
require_relative 'test_helper'
class ParticipantRepositoryTest < MiniTest::Unit::TestCase
def setup()
#repository = ParticipantRepository.instance
end
def test_retrieve_participant
participant = #repository.generate_participant
refute_nil participant
refute_nil participant.name
refute_equal("", participant.name)
assert_equal(0, participant.subordinates_count)
end
def test_employee_name_increment
participant1 = #repository.generate_participant
participant2 = #repository.generate_participant
refute_equal(participant1.name, participant2.name)
index_participant1 = /Employee([0-9]+)/.match(participant1.name)[1]
index_participant2 = /Employee([0-9]+)/.match(participant2.name)[1]
assert_equal(0, index_participant1.to_i)
assert_equal(1, index_participant2.to_i)
end
end
The assertion assert_equal(0, index_participant1.to_i) succeeds when test_employee_name_increment is executed first and fails if it is executed last.
I would like to be able to test the repository (because it will evolve into something bigger). How can I do that?
Thanks!
Ordering your tests won't matter. To properly test a singleton class you need to treat it like an instance object. To do that, wrap your singleton in an anonymous Class during setup. Every time setup is called you'll get an untouched copy of the ParticipantRepository:
def setup
#repository = Class.new(ParticipantRepository).instance
end
Call i_suck_and_my_tests_are_order_dependent!() at the top of your
tests when you absolutely positively need to have ordered tests. In
doing so, you’re admitting that you suck and your tests are weak.
...I promise I didn't write this method or the docs.
For more info, see: http://www.ruby-doc.org/stdlib-2.0/libdoc/minitest/rdoc/MiniTest/Unit/TestCase.html#method-c-i_suck_and_my_tests_are_order_dependent-21

Resources