Today I've started with some basic implementation of minitest and finally figured a way out to test if a method on a class is called twice.
In RSpec I would do something like:
expect(#foo).to receive(:some_heavy_calculation).once
2.times { #foo.bar }
Now, I've came up with the following implementation for MiniTest, but I'm not sure if this is the way to implement this, because this. Here's what I've got
require 'minitest/autorun'
class Foo
def bar
#cached_value ||= some_heavy_calculation
end
def some_heavy_calculation
"result"
end
end
class FooTest < Minitest::Test
def setup
#foo = Foo.new
end
def cache_the_value_when_calling_bar_twice
mock = Minitest::Mock.new
mock.expect(:some_heavy_calculation, [])
#foo.stub :some_heavy_calculation, -> { mock.some_heavy_calculation } do
2.times { assert_equal_set #foo.bar, [] }
end
mock.verify
end
end
Do I really have to implement this with a mock, which will be the result of the stub of the subject on the method that has to be called x times?
I had to do something similar. This is what I ended up with...
def cache_the_value_when_calling_bar_twice
count = 0
#foo.stub :some_heavy_calculation, -> { count += 1 } do
2.times { assert_equal_set #foo.bar, [] }
end
assert_equal 1, count
end
I did something like this in one of my tests. This also works if your method is potentially invoking multiple instances of a class:
test "verify number of method calls" do
count = 0
Foo.stub_any_instance(:some_heavy_calculation, -> { count += 1 }) do
2.times { assert_equal_set #foo.bar, [] }
end
assert_equal 1, count
end
Related
I have a class that implements a .call method that yields an object to the block and I would like to learn how to write a unit test for this. Here's what I have.
module A
class B < Service
def call(object_id:)
#object = Something.find(object_id)
#object.update(status: 'done')
yield #object
end
def set_to_in_progress
#object.update(status: 'in_progress')
end
end
end
class Service
def self.call(*args); new.call(*args); end
end
Then I use it like this:
A::B.call(obj) do |object|
object.set_to_in_progress if some_other_condition?
end
I need to be able to write a unit test for call method that tests whether the status has changed to done or in progress. Here's what I have:
RSpec.describe A::B, :unit do
let(:object) { create(:something, id: 1, status: 'in_progress') }
it 'updates the status to done' do
described_class.call(object.id) do |???|
???
end
expect(object.status).to equal('done')
end
it 'updates the status to in progress' do
described_class.call(object.id) do |???|
???
end
expect(object.status).to equal('in_progress')
end
end
You could return the object in #call:
def call(object_id:)
#object = Something.find(object_id)
#object.update(status: 'done')
yield #object
#object
end
Then in the test you can do:
object = described_class.call(object.id)
expect(object.status).to eq('in_progress')
You are looking for yield_with_args matcher:
specify do
expect { |block| described_class.call(object.id, &block) }
.to yield_with_args(object)
end
Just a side note that you're mixing testing of behaviour (yields object) and side effects (object has been changed).
I have a section in my code which polls a queue for a message and then acts on it depending on the message type:
#queue = Foo::Queue.new
loop do
#queue.poll do |message|
if message[:task] == TAKEACTION
result = takeaction(message)
#queue.send_result(result, message)
end
#queue.pong(message) if message[:task] == PING
end
end
How do I set up a test to supply a single message and verify that the #queue acts on it as I expect?
I have seen very little about testing blocks in minitest, and haven't found anything in ruby regarding breaking out of infinite loops, though I found one idea in python where you set up the second run to throw an exception.
Can any ruby / minitest gurus help?
For minitest using a stub will work. The example below is self contained and can run on its own. You can send an exception as a lambda with a stub to break the infinite loop and continue testing.
# class receiving the messages from class being tested
class Obj
def method_if_true
# do some stuff
return 'anything that is true'
end
def method_if_false
# do some stuff
false
end
end
# class to be tested
class TestingLoop
def initialize(args)
#obj = args.fetch(:object, Obj.new)
#bool = args[:bool]
end
def looping
loop do
if #bool
#obj.method_if_true
# #bool is true
elsif !#bool
#obj.method_if_false
# #bool is false
end
end
end
end
require 'minitest/autorun'
# class that tests
class TestTestingLoop < MiniTest::Test
def setup
#obj = Obj.new
#raises_exception = lambda { raise RuntimeError.new }
# could use the '->' syntax instead of the word lambda
end
def test_sends_correct_method_when_true
#obj.stub :method_if_true, #raises_exception do
testing_loop = TestingLoop.new({object: #obj, bool: true})
assert_raises(RuntimeError) { testing_loop.looping }
end
end
def test_sends_correct_method_when_false
#obj.stub :method_if_false, #raises_exception do
testing_loop = TestingLoop.new({object: #obj, bool: false})
assert_raises(RuntimeError) { testing_loop.looping }
end
end
end
I'm stuck with this exercise from exercism.io:
part of sum_of_multiples_test.rb
...
def test_sum_to_1000
skip
assert_equal 233168, SumOfMultiples.to(1000)
end
def test_configurable_7_13_17_to_20
assert_equal 51, SumOfMultiples.new(7, 13, 17).to(20)
end
...
sum.rb
class SumOfMultiples
def initialize(*args)
#args = args ||= [3,5]
end
def to(max)
ary = []
return 0 if max < 2
#args.each do |m|
for i in 0..max-1
ary << i if i % m == 0
end
end
ary.uniq!.inject(:+)
end
end
If I use class method self.to, it can't see my instance variable #args, if I use
instance method "def to" first test don't pass. Is there a way to somehow "merge" both?
Add another method to your class:
def self.to(max)
new.to(max)
end
You can now call each of these and the result will be the same:
SumOfMultiples.to(1000)
SumOfMultiples.new.to(1000)
I was looking in detail at the Thread class. Basically, I was looking for an elegant mechanism to allow thread-local variables to be inherited as threads are created. For example the functionality I am looking to create would ensure that
Thread.new do
self[:foo]="bar"
t1=Thread.new { puts self[:foo] }
end
=> "bar"
i.e. a Thread would inherit it's calling thread's thread-local variables
So I hit upon the idea of redefining Thread.new, so that I could add an extra step to copy the thread-local variables into the new thread from the current thread. Something like this:
class Thread
def self.another_new(*args)
o=allocate
o.send(:initialize, *args)
Thread.current.keys.each{ |k| o[k]=Thread.current[k] }
o
end
end
But when I try this I get the following error:
:in `allocate': allocator undefined for Thread (TypeError)
I thought that as Thread is a subclass of Object, it should have a working #allocate method. Is this not the case?
Does anyone have any deep insight on this, and on how to achieve the functionality I am looking for.
Thanks in advance
Steve
Thread.new do
Thread.current[:foo]="bar"
t1=Thread.new(Thread.current) do |parent|
puts parent[:foo] ? parent[:foo] : 'nothing'
end.join
end.join
#=> bar
UPDATED:
Try this in irb:
thread_ext.rb
class Thread
def self.another_new(*args)
parent = Thread.current
a = Thread.new(parent) do |parent|
parent.keys.each{ |k| Thread.current[k] = parent[k] }
yield
end
a
end
end
use_case.rb
A = Thread.new do
Thread.current[:local_a]="A"
B1 =Thread.another_new do
C1 = Thread.another_new{p Thread.current[:local_a] }.join
end
B2 =Thread.another_new do
C2 = Thread.another_new{p Thread.current[:local_a] }.join
end
[B1, B2].each{|b| b.join }
end.join
output
"A"
"A"
Here is a revised answer based on #CodeGroover's suggestion, with a simple unit test harness
ext/thread.rb
class Thread
def self.inherit(*args, &block)
parent = Thread.current
t = Thread.new(parent, *args) do |parent|
parent.keys.each{ |k| Thread.current[k] = parent[k] }
yield *args
end
t
end
end
test/thread.rb
require 'test/unit'
require 'ext/thread'
class ThreadTest < Test::Unit::TestCase
def test_inherit
Thread.current[:foo]=1
m=Mutex.new
#check basic inheritence
t1= Thread.inherit do
assert_equal(1, Thread.current[:foo])
end
#check inheritence with parameters - in this case a mutex
t2= Thread.inherit(m) do |m|
assert_not_nil(m)
m.synchronize{ Thread.current[:bar]=2 }
assert_equal(1, Thread.current[:foo])
assert_equal(2, Thread.current[:bar])
sleep 0.1
end
#ensure t2 runs its mutexs-synchronized block first
sleep 0.05
#check that the inheritence works downwards only - not back up in reverse
m.synchronize do
assert_nil(Thread.current[:bar])
end
[t1,t2].each{|x| x.join }
end
end
I was looking for the same thing recently and was able to come up with the following answer. Note I am aware the following is a hack and not recommended, but for the sake of answering the specific question on how you could alter the Thread.new functionality, I have done as following:
class Thread
class << self
alias :original_new :new
def new(*args, **options, &block)
original_thread = Thread.current
instance = original_new(*args, **options, &block)
original_thread.keys.each do |key|
instance[key] = original_thread[key]
end
instance
end
end
end
I have this function:
def file_parser (filename)
Enumerator.new do |yielder|
File.open(filename, "r:ISO-8859-1") do |file|
csv = CSV.new(file, :col_sep => "\t", :headers => true, :quote_char => "\x07")
csv.each do |row|
yielder.yield map_fields(clean_data(row.to_hash))
end
end
end
end
I can use it like this:
parser = file_parser("data.tab")
parser.each do { |data| do_profitable_things_with data }
Instead, I'd like to put it in its own class and use it like this:
parser = SpecialParser.new("data.tab")
parser.each do { |data| do_profitable_things_with data }
I've tried some things I didn't expect to work, like just returning the enumerator out of initialize(), and self = file_parser().
I've also tried super do |yielder|.
For some reason, the way to do this is not coming to me.
You can just include the Enumerable module into your class, and define an each function which calls yield.
You still get all the Enumerable methods like map, reduce, etc, for free.
class SpecialParser
include Enumerable
def initialize(n)
#n = n
end
def each
0.upto(#n) { |i| yield i }
end
end
sp = SpecialParser.new 4
sp.each { |i| p i }
p sp.map { |i| i }
Output:
0
1
2
3
4
[0, 1, 2, 3, 4]
Make file_parser a private method in SpecialParser.
Then set up the rest of the class like this:
class SpecialParser
include Enumerable # needed to provide the other Enumerable methods
def initialize(filename)
#filename = filename
#enum = file_parser(filename)
end
def each
#enum.each do |val|
yield val
end
end
end
EDIT:
If you want the other Enumerable method for free, you also have to include Enumerable in the class.