Ruby MiniTest UnitTest Stubbing Class method just for one test - ruby

I want to stub a class method for just one test and for the rest of the tests, i want the actual method to be called. I have always been using rspec and mocha, so the below behavior looks bizarre.
The class that i want to stub in one of my tests.
class MyClass
def self.foo(arg)
return "foo#{arg}"
end
end
The test where i try to stub MyClass.foo
class XYZTest < Minitest::Test
def test_1
MyClass.expects(:foo).returns('abcd')
assert_equal MyClass.foo('123'), 'abcd'
end
def test_2
assert_equal MyClass.foo('123'), 'foo123'
end
end
The first test passes, but the second test fails stating Mocha::ExpectationError: unexpected invocation: MyClass.foo('123')
In the test_2, i want the actual class method to be called instead of the stub that i did in test_1.
PS: Above is a striped down example. I do not want to reset everytime, I stub the class method.

Minitest stubs methods within a block, so what you're trying to do is simple.
class XYZTest < Minitest::Test
# stubbed here
def test_1
MyClass.stub(:foo, 'abcd') do
assert_equal MyClass.foo('123'), 'abcd'
end
end
# not stubbed here
def test_2
assert_equal MyClass.foo('123'), 'foo123'
end
end

Related

Minitest how to solve mocked method :call expects 0 arguments, got 1

I have a service with the call method to stub. But its always have this error:
SomeTest#test_1:
ArgumentError: mocked method :call expects 0 arguments, got 1
test.rb:17:in `block in test_1'
test.rb:16:in `test_1'
Here's the code:
require 'minitest/autorun'
class SomeService
def initialize(headers = {})
end
def call
end
end
class SomeTest < Minitest::Test
def test_1
mock = Minitest::Mock.new
mock.expect :call, nil
SomeService.stub :new, mock do
SomeService.new({}).call
end
end
end
What did I do wrong? thanks
In general what you wrote is right but you hit and edge case with the method name call.
As you can see in the documentation of stub method.
"If val_or_callable responds to #call, then it returns the result of calling it".
So basically the problem is your mock responds to call, and the stub method is calling it before entering the block.
You can solve it by wrapping the mock in a lambda that discards the arguments.
require 'minitest/autorun'
class SomeService
def initialize(headers = {})
end
def call
end
end
class SomeTest < Minitest::Test
def test_1
mock = Minitest::Mock.new
mock.expect :call, nil
SomeService.stub :new, ->(_args) { mock } do
SomeService.new({}).call
end
end
end

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 can I test delegating methods using SimpleDelegator and RSpec?

I'm using Ruby 1.9.3 and trying to make some tests with RSpec.
I have a class:
class A
def method1
"test"
end
end
class B < SimpleDelegator
def initialize(events)
#events = events
end
end
Now I'm trying to test delegation behaviour:
require 'spec_helper'
RSpec.describe B do
let(:a) { A.new }
let(:b) { B.new(a) }
it "Should delegate unknown calls to A object" do
expect(b.method1).not_to eq(nil)
end
end
I get the following error:
NoMethodError:
undefined method `method1' for nil:B
Seems that the test would pass if add method_missing manually:
class B < SimpleDelegator
def initialize(events)
#events = events
end
def method_missing(meth, *args, &blk)
#events.send(meth, *args, &blk)
end
end
What I'm doing wrong here?
Thanks
The problem is that you added a initializer to the class B without calling super and passing the instance you want to decorate. Your code should look like this:
class A
def method1
"test"
end
end
class B < SimpleDelegator
def initialize(events)
#events = events
super(events)
end
end
You don't need to define an initialize method on B. SimpleDelegator defines one for you. When you defined your own initialize method, you overrode the initialize method you inherited from the SimpleDelegator class.
Try this:
class A
def method1
"test"
end
end
class B < SimpleDelegator
end
This is from irb: B.new(A.new).method1 #=> "test"
You could define your own initialize method and call super, but I wouldn't unless you really had to.

Decorating all methods of a class

Say I have a class Caller that calls another class's methods (i.e. Abc) in ruby:
class Caller
def run
abc = Abc.new
abc.method1
abc.method2
end
end
class Abc
def method1
puts 'Method1 etc'
end
def method2
puts 'Method2 etc'
end
end
caller = Caller.new
caller.run
Any time a method in class Abc is called, I need to decorate the call with a prefix that shows the Calling method class name and method name
E.g. in the example above, I need the following output:
Caller.run - Method1 etc
Caller.run - Method2 etc
What is the best way to do this in ruby?
You can create decorator that will not define any particular method, but will implement method_missing hook, and wrap every call in whatever code you need:
class Caller
def initialize(object)
#object = object
end
def method_missing(meth, *args, &block)
puts 'wrapper'
#object.public_send(meth, *args, &block)
end
end
class YourClass
def method1
puts "method 1"
end
end
c = Caller.new(YourClass.new)
c.method1
This way your decorator is unobtrusive. Moreover you can control which method calls are wrapped (e.g. by defining whitelist or blacklist in method_missing). This is quite clear way of defining aspects of behavior in well separated blocks of code.

In Ruby's Test::Unit::TestCase, how do I override the initialize method?

I'm struggling with Test::Unit. When I think of unit tests, I think of one simple test per file. But in Ruby's framework, I must instead write:
class MyTest < Test::Unit::TestCase
def setup
end
def test_1
end
def test_1
end
end
But setup and teardown run for every invocation of a test_* method. This is exactly what I don't want. Rather, I want a setup method that runs just once for the whole class. But I can't seem to write my own initialize() without breaking TestCase's initialize.
Is that possible? Or am I making this hopelessly complicated?
As mentioned in Hal Fulton's book "The Ruby Way".
He overrides the self.suite method of Test::Unit which allows the test cases in a class to run as a suite.
def self.suite
mysuite = super
def mysuite.run(*args)
MyTest.startup()
super
MyTest.shutdown()
end
mysuite
end
Here is an example:
class MyTest < Test::Unit::TestCase
class << self
def startup
puts 'runs only once at start'
end
def shutdown
puts 'runs only once at end'
end
def suite
mysuite = super
def mysuite.run(*args)
MyTest.startup()
super
MyTest.shutdown()
end
mysuite
end
end
def setup
puts 'runs before each test'
end
def teardown
puts 'runs after each test'
end
def test_stuff
assert(true)
end
end
FINALLY, test-unit has this implemented! Woot!
If you are using v 2.5.2 or later, you can just use this:
Test::Unit.at_start do
# initialization stuff here
end
This will run once when you start your tests off. There are also callbacks which run at the beginning of each test case (startup), in addition to the ones that run before every test (setup).
http://test-unit.rubyforge.org/test-unit/en/Test/Unit.html#at_start-class_method
That's how it's supposed to work!
Each test should be completely isolated from the rest, so the setup and tear_down methods are executed once for every test-case. There are cases, however, when you might want more control over the execution flow. Then you can group the test-cases in suites.
In your case you could write something like the following:
require 'test/unit'
require 'test/unit/ui/console/testrunner'
class TestDecorator < Test::Unit::TestSuite
def initialize(test_case_class)
super
self << test_case_class.suite
end
def run(result, &progress_block)
setup_suite
begin
super(result, &progress_block)
ensure
tear_down_suite
end
end
end
class MyTestCase < Test::Unit::TestCase
def test_1
puts "test_1"
assert_equal(1, 1)
end
def test_2
puts "test_2"
assert_equal(2, 2)
end
end
class MySuite < TestDecorator
def setup_suite
puts "setup_suite"
end
def tear_down_suite
puts "tear_down_suite"
end
end
Test::Unit::UI::Console::TestRunner.run(MySuite.new(MyTestCase))
The TestDecorator defines a special suite which provides a setup and tear_down method which run only once before and after the running of the set of test-cases it contains.
The drawback of this is that you need to tell Test::Unit how to run the tests in the unit. In the event your unit contains many test-cases and you need a decorator for only one of them you'll need something like this:
require 'test/unit'
require 'test/unit/ui/console/testrunner'
class TestDecorator < Test::Unit::TestSuite
def initialize(test_case_class)
super
self << test_case_class.suite
end
def run(result, &progress_block)
setup_suite
begin
super(result, &progress_block)
ensure
tear_down_suite
end
end
end
class MyTestCase < Test::Unit::TestCase
def test_1
puts "test_1"
assert_equal(1, 1)
end
def test_2
puts "test_2"
assert_equal(2, 2)
end
end
class MySuite < TestDecorator
def setup_suite
puts "setup_suite"
end
def tear_down_suite
puts "tear_down_suite"
end
end
class AnotherTestCase < Test::Unit::TestCase
def test_a
puts "test_a"
assert_equal("a", "a")
end
end
class Tests
def self.suite
suite = Test::Unit::TestSuite.new
suite << MySuite.new(MyTestCase)
suite << AnotherTestCase.suite
suite
end
end
Test::Unit::UI::Console::TestRunner.run(Tests.suite)
The Test::Unit documentation documentation provides a good explanation on how suites work.
Well, I accomplished basically the same way in a really ugly and horrible fashion, but it was quicker. :) Once I realized that the tests are run alphabetically:
class MyTests < Test::Unit::TestCase
def test_AASetup # I have a few tests that start with "A", but I doubt any will start with "Aardvark" or "Aargh!"
#Run setup code
end
def MoreTests
end
def test_ZTeardown
#Run teardown code
end
It aint pretty, but it works :)
To solve this problem I used the setup construct, with only one test method followed. This one testmethod is calling all other tests.
For instance
class TC_001 << Test::Unit::TestCase
def setup
# do stuff once
end
def testSuite
falseArguments()
arguments()
end
def falseArguments
# do stuff
end
def arguments
# do stuff
end
end
I know this is quite an old post, but I had the issue (and had already written classes using Tes/unit) and ave answered using another method, so if it can help...
If you only need the equivalent of the startup function, you can use the class variables:
class MyTest < Test::Unit::TestCase
##cmptr = nil
def setup
if ##cmptr.nil?
##cmptr = 0
puts "runs at first test only"
##var_shared_between_fcs = "value"
end
puts 'runs before each test'
end
def test_stuff
assert(true)
end
end
I came across this exact problem and created a subclass of Test::Unit::TestCase for doing exactly what you describe.
Here's what I came up with. It provides it's own setup and teardown methods that count the number of methods in the class that begin with 'test'. On the first call to setup it calls global_setup and on the last call to teardown it calls global_teardown
class ImprovedUnitTestCase < Test::Unit::TestCase
cattr_accessor :expected_test_count
def self.global_setup; end
def self.global_teardown; end
def teardown
if((self.class.expected_test_count-=1) == 0)
self.class.global_teardown
end
end
def setup
cls = self.class
if(not cls.expected_test_count)
cls.expected_test_count = (cls.instance_methods.reject{|method| method[0..3] != 'test'}).length
cls.global_setup
end
end
end
Create your test cases like this:
class TestSomething < ImprovedUnitTestCase
def self.global_setup
puts 'global_setup is only run once at the beginning'
end
def self.global_teardown
puts 'global_teardown is only run once at the end'
end
def test_1
end
def test_2
end
end
The fault in this is that you can't provide your own per-test setup and teardown methods unless you use the setup :method_name class method (only available in Rails 2.X?) and if you have a test suite or something that only runs one of the test methods, then the global_teardown won't be called because it assumes that all the test methods will be run eventually.
Use the TestSuite as #romulo-a-ceccon described for special preparations for each test suite.
However I think it should be mentioned here that Unit tests are ment to run in total isolation. Thus the execution flow is setup-test-teardown which should guarantee that each test run undisturbed by anything the other tests did.
I created a mixin called SetupOnce. Here's an example of using it.
require 'test/unit'
require 'setuponce'
class MyTest < Test::Unit::TestCase
include SetupOnce
def self.setup_once
puts "doing one-time setup"
end
def self.teardown_once
puts "doing one-time teardown"
end
end
And here is the actual code; notice it requires another module available from the first link in the footnotes.
require 'mixin_class_methods' # see footnote 1
module SetupOnce
mixin_class_methods
define_class_methods do
def setup_once; end
def teardown_once; end
def suite
mySuite = super
def mySuite.run(*args)
#name.to_class.setup_once
super(*args)
#name.to_class.teardown_once
end
return mySuite
end
end
end
# See footnote 2
class String
def to_class
split('::').inject(Kernel) {
|scope, const_name|
scope.const_get(const_name)
}
end
end
Footnotes:
http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html
http://infovore.org/archives/2006/08/02/getting-a-class-object-in-ruby-from-a-string-containing-that-classes-name/
+1 for the RSpec answer above by #orion-edwards. I would have commented on his answer, but I don't have enough reputation yet to comment on answers.
I use test/unit and RSpec a lot and I have to say ... the code that everyone has been posting is missing a very important feature of before(:all) which is: #instance variable support.
In RSpec, you can do:
describe 'Whatever' do
before :all do
#foo = 'foo'
end
# This will pass
it 'first' do
assert_equal 'foo', #foo
#foo = 'different'
assert_equal 'different', #foo
end
# This will pass, even though the previous test changed the
# value of #foo. This is because RSpec stores the values of
# all instance variables created by before(:all) and copies
# them into your test's scope before each test runs.
it 'second' do
assert_equal 'foo', #foo
#foo = 'different'
assert_equal 'different', #foo
end
end
The implementations of #startup and #shutdown above all focus on making sure that these methods only get called once for the entire TestCase class, but any instance variables used in these methods would be lost!
RSpec runs its before(:all) in its own instance of Object and all of the local variables are copied before each test is run.
To access any variables that are created during a global #startup method, you would need to either:
copy all of the instance variables created by #startup, like RSpec does
define your variables in #startup into a scope that you can access from your test methods, eg. ##class_variables or create class-level attr_accessors that provide access to the #instance_variables that you create inside of def self.startup
Just my $0.02!

Resources