Is there a way to mock/stub "puts" in Rails - ruby

I am printing some custom messages in my application using the puts command. However, I do not want these to be appearing in my Test Output. So, I tried a way to stub puts as shown below. But it still outputs my messages. What am I doing wrong ?
stubs(:puts).returns("") #Did not work out
Object.stubs(:puts).returns("") #Did not work out either
puts.stubs.returns "" #Not working as well
Kernel.stubs(:puts).returns "" #No luck
I am using Test::Unit

You probably need to stub it on the actual instance that calls puts. E.g. if you're calling puts in an instance method of a User class, try:
user = User.new
user.stubs(:puts)
user.some_method_that_calls_puts
This similarly applies to when you're trying to test puts in the top-level execution scope:
self.stubs(:puts)

What I would do is define a custom log method (that essentially calls puts for now) which you can mock or silence in test quite easily.
This also gives you the option later to do more with it, like log to a file.
edit: Or if you really want to stub puts, and you are calling it inside an instance method for example, you can just stub puts on the instance of that class.

Using Rails 5 + Mocha: $stdout.stubs(puts: '')

So the comments to the original post point to the answer:
Kernel.send(:define_method, :puts) { |*args| "" }
Instead of silencing all output, I would only silence output from the the particular objects that are putsing during your tests.
class TestClass
def some_method
...
puts "something"
end
end
it "should do something expected" do
TestClass.send(:define_method, :puts) { |*args| "" }
test_class.some_method.should == "abc123"
end

Related

How to properly stub doubles

Code being tested:
class Session
def initialize
#interface = Interface.new(self)
#interface.hello
end
end
class Interface
def initialize(session, out = $STDOUT)
#session = session
#out = out
end
def hello
#out.puts "hello"
end
end
Test:
describe Session do
let (:fake_stdout) {double("$STDOUT", :puts => true)}
let (:interface) {instance_double("Interface", :out => "fake_stdout")}
let (:session) { Session.new }
describe "#new" do
it "creates an instance of Session" do
expect(session).to be_an_instance_of(Session)
end
end
end
This throws private method 'puts' called for nil:NilClass. It seems it's not seeing the fake_stdout with its specified :puts as out. I tried tying it with allow(Interface).to receive(:new).with(session).and_return(interface), but that changed nothing. How do I get the tested Session class to see the double/instance double and pass the test?
I think, this is not really problem with stubbing, but the general approach. When writing your unit tests for some class, you should stick to functionality of that class and eventually to API it sees. If you're stubbing "internal" out of Interface - it's already to much for specs of Session.
What Session really sees, is Interfaces public hello method, thus Session spec, should not be aware of internal implementation of it (that it is #out.puts "hello"). The only thing you should really focus is that, the hello method has been called. On the other hand, ensuring that the put is called for hello should be described in specs for Interface.
Ufff... That's long introduction/explanation, but how to proceed then? (known as show me the code! too ;)).
Having said, that Session.new should be aware only of Interfaces hello method, it should trust it works properly, and Sessions spec should ensure that the method is called. For that, we'll use a spy. Let's get our hand dirty!
RSpec.describe Session do
let(:fake_interface) { spy("interface") }
let(:session) { Session.new }
before do
allow(Interface).to receive(:new).and_return(fake_interface)
end
describe "#new" do
it "creates an instance of Session" do
expect(session).to be_an_instance_of(Session) # this works now!
end
it "calls Interface's hello method when initialized" do
Session.new
expect(fake_interface).to have_received(:hello)
end
end
end
A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls.
This is taken from SinonJS (which is the first result when googling for "what is test spy"), but explanation is accurate.
How does this work?
Session.new
expect(fake_interface).to have_received(:hello)
First of all, we're executing some code, and after that we're asserting that expected things happened. Conceptually, we want to be sure, that during Session.new, the fake_interface have_received(:hello). That's all!
Ok, but I need another test ensuring that Interfaces method is called with specific argument.
Ok, let's test that!
Assuming the Session looks like:
class Session
def initialize
#interface = Interface.new(self)
#interface.hello
#interface.say "Something More!"
end
end
We want to test say:
RSpec.describe Session do
describe "#new" do
# rest of the code
it "calls interface's say_something_more with specific string" do
Session.new
expect(fake_interface).to have_received(:say).with("Something More!")
end
end
end
This one is pretty straightforward.
One more thing - my Interface takes a Session as an argument. How to test that the interface calls sessions method?
Let's take a look at sample implementation:
class Interface
# rest of the code
def do_something_to_session
#session.a_session_method
end
end
class Session
# ...
def another_method
#interface.do_something_to_session
end
def a_session_method
# some fancy code here
end
end
It won't be much surprise, if I say...
RSpec.describe Session do
# rest of the code
describe "#do_something_to_session" do
it "calls the a_session_method" do
Session.new.another_method
expect(fake_interface).to have_received(:do_something_to_session)
end
end
end
You should check, if Sessions another_method called interfaces do_something_to_session method.
If you test like this, you make the tests less fragile to future changes. You might change an implementation of Interface, that it doesn't rely on put any more. When such change is introduced - you have to update the tests of Interface only. Session knows only the proper method is called, but what happens inside? That's the Interfaces job...
Hope that helps! Please, take a look at another example of spy in my other answer.
Good luck!

Rspec 3.0 How to mock a method replacing the parameter but with no return value?

I've searched a lot and just cannot figure this out although it seems basic. Here's a way simplified example of what I want to do.
Create a simple method that does something but doesn't return anything, such as:
class Test
def test_method(param)
puts param
end
test_method("hello")
end
But in my rspec test I need to pass a different parameter, such as "goodbye" instead of "hello." I know this has to do with stubs and mocks, and I've looking over the documentation but can't figure it out: https://relishapp.com/rspec/rspec-mocks/v/3-0/docs/method-stubs
If I do:
#test = Test.new
allow(#test).to_receive(:test_method).with("goodbye")
it tells me to stub out a default value but I can't figure out how to do it correctly.
Error message:
received :test_method with unexpected arguments
expected: ("hello")
got: ("goodbye")
Please stub a default value first if message might be received with other args as well.
I am using rspec 3.0, and calling something like
#test.stub(:test_method)
is not allowed.
How to set a default value that is explained at
and_call_original can configure a default response that can be overriden for specific args
require 'calculator'
RSpec.describe "and_call_original" do
it "can be overriden for specific arguments using #with" do
allow(Calculator).to receive(:add).and_call_original
allow(Calculator).to receive(:add).with(2, 3).and_return(-5)
expect(Calculator.add(2, 2)).to eq(4)
expect(Calculator.add(2, 3)).to eq(-5)
end
end
Source where I came to know about that can be found at https://makandracards.com/makandra/30543-rspec-only-stub-a-method-when-a-particular-argument-is-passed
For your example, since you don't need to test the actual result of test_method, only that puts gets called in it passing in param, I would just test by setting up the expectation and running the method:
class Test
def test_method(param)
puts param
end
end
describe Test do
let(:test) { Test.new }
it 'says hello via expectation' do
expect(test).to receive(:puts).with('hello')
test.test_method('hello')
end
it 'says goodbye via expectation' do
expect(test).to receive(:puts).with('goodbye')
test.test_method('goodbye')
end
end
What it seems you're attempting to do is set up a test spy on the method, but then I think you're setting up the method stub one level too high (on test_method itself instead of the call to puts inside test_method). If you put the stub on the call to puts, your tests should pass:
describe Test do
let(:test) { Test.new }
it 'says hello using a test spy' do
allow(test).to receive(:puts).with('hello')
test.test_method('hello')
expect(test).to have_received(:puts).with('hello')
end
it 'says goodbye using a test spy' do
allow(test).to receive(:puts).with('goodbye')
test.test_method('goodbye')
expect(test).to have_received(:puts).with('goodbye')
end
end

Rspec Ruby Mocking

I would like to achieve 100% coverage on a module. My problem is that there is a variable (called data) within a method which I am trying to inject data in to test my exception handling. Can this be done with mocking? If not how can i fully test my exception handling?
module CSV
module Extractor
class ConversionError < RuntimeError; end
class MalformedCSVError < RuntimeError; end
class GenericParseError < RuntimeError; end
class DemoModeError < RuntimeError; end
def self.open(path)
data = `.\\csv2text.exe #{path} -f xml --xml_output_styles 2>&1`
case data
when /Error: Wrong input filename or path:/
raise MalformedCSVError, "the CSV path with filename '#{path}' is malformed"
when /Error: A valid password is required to open/
raise ConversionError, "Wrong password: '#{path}'"
when /CSVTron CSV2Text: This page is skipped when running in the demo mode./
raise DemoModeError, "CSV2TEXT.exe in demo mode"
when /Error:/
raise GenericParseError, "Generic Error Catch while reading input file"
else
begin
csvObj = CSV::Extractor::Document.new(data)
rescue
csvObj = nil
end
return csvObj
end
end
end
end
Let me know what you think! Thanks
===================== EDIT ========================
I have modified my methods to the design pattern you suggested. This method-"open(path)" is responsible for trapping and raising errors, get_data(path) just returns data, That's it! But unfortunately in the rspec I am getting "exception was expected to be raise but nothing was raised." I thought maybe we have to call the open method from your stub too?
This is what I tried doing but still no error was raised..
it 'should catch wrong path mode' do
obj = double(CSV::Extractor)
obj.stub!(:get_data).and_return("Error: Wrong input filename or path:")
obj.stub!(:open)
expect {obj.open("some fake path")}.to raise_error CSV::Extractor::MalformedCSVError
end
Extract the code that returns the data to a separate method. Then when you test open you can stub out that method to return various strings that will exercise the different branches of the case statement. Roughly like this for the setup:
def self.get_data(path)
`.\\csv2text.exe #{path} -f xml --xml_output_styles 2>&1`
end
def self.open(path)
data = get_data(path)
...
And I assume you know how to stub methods in rspec, but the general idea is like this:
foo = ...
foo.stub(:get_data).and_return("Error: Wrong input filename or path:")
expect { foo.get_data() }.to raise_error MalformedCSVError
Also see the Rspec documentation on testing for exceptions.
Problem with testing your module lies in the way you have designed your code. Think about splitting extractor into two classes (or modules, it's matter of taste -- I'd go with classes as they are a bit easier to test), of which one would read data from external system call, and second would expect this data to be passed as an argument.
This way you can easily mock what you currently have in data variable, as this would be simply passed as an argument (no need to think about implementation details here!).
For easier usage you can later provide some wrapper call, that would create both objects and pass one as argument to another. Please note, that this behavior can also be easily tested.

rails rspec - how to check for a model constant?

How can I do something like:
it { should have_constant(:FIXED_LIST) }
In my model (active record) I have FIXED_LIST = 'A String'
It's not a db attribute or a method and I haven't been able to use responds_to or has_attribute to test for it (they fail). What can I use the to check for it. - btw I have the shoulda-matchers installed.
Based on David Chelimsky's answer I've got this to work by slightly modifying his code.
In a file spec/support/utilities.rb (or some other in spec/support) you can put:
RSpec::Matchers.define :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
Note the use of "RSpec::Matchers.define" in stead of "matchers"
This allows to test for constants in your specs, like:
it "should have a fixed list constant" do
YourModel.should have_constant(:FIXED_LIST)
end
Note the use of "have_constant" in stead of "have_const"
It reads a little silly, but:
describe MyClass do
it { should be_const_defined(:VERSION) }
end
The reason is that Rspec has "magic" matchers for methods starting with be_ and have_. For example, it { should have_green_pants } would assert that the has_green_pants? method on the subject returns true.
In the same fashion, an example such as it { should be_happy } would assert that the happy? method on the subject returns true.
So, the example it { should be_const_defined(:VERSION) } asserts that const_defined?(:VERSION) returns true.
If you want to say have_constant you can define a custom matcher for it:
matcher :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
MyClass.should have_const(:CONST)
If you're trying to use the one-liner syntax, you'll need to make sure the subject is a class (not an instance) or check for it in the matcher:
matcher :have_constant do |const|
match do |owner|
(owner.is_a?(Class) ? owner : owner.class).const_defined?(const)
end
end
See http://rubydoc.info/gems/rspec-expectations/RSpec/Matchers for more info on custom matchers.
HTH,
David
Another option to simply make sure the constant is defined – not worrying about what it's defined with:
it 'has a WHATEVER constant' do
expect(SomeClass::WHATEVER).not_to be_nil
end
A warning to anyone trying to test that constants are defined: If your code references an undefined constant while defining a class, then your specs will crash before they get to your test.
This can lead you to believe that
expect { FOO }.to_not raise_error
is failing to catch the NameError, because you'll get a big stack trace, instead of a nice "expected not to raise error, but raised NameError."
Amidst the huge stack trace, it can be difficult to notice that your test is actually crashing on line 1: requre "spec/spec_helper" because your entire application is failing to load before it gets to your actual test.
This can happen if you have dynamically defined constants, such as is done by ActiveHash::Enum, and you then use them in the definition of another constant. Don't bother testing that they exist, every spec in your app will crash if one of them fails to be defined.
You could use
defined? YOUR_MODEL::FIXED_LIST
In RSpec 2, I was able to get this to work in one line as follows:
it { subject.class.should be_const_defined(:MY_CONST) }
That is, check against the class, instead of the instance.
In My model
class Role < ActiveRecord::Base
ROLE_ADMIN = "Administrador"
end
In My rspec
RSpec.describe Role, type: :model do
let(:fake_class) { Class.new }
describe "set constants" do
before { stub_const("#{described_class}", fake_class) }
it { expect(described_class::ROLE_ADMIN).to eq("Administrador") }
end
end
For ruby 2.1.5 and rspec 3.5.0 I am able to test that constant SEARCH_CHARS_TO_IGNORE is defined in the class DiffAlertsDatatable as follows:
expect(DiffAlertsDatatable.const_defined?(:SEARCH_CHARS_TO_IGNORE)).to eq(true)

Mock system call in ruby

Know of a way to mock %[]? I'm writing tests for code that makes a few system calls, for example:
def log(file)
%x[git log #{file}]
end
and would like to avoid actually executing system calls while testing this method. Ideally I'd like to mock %x[..] and assert that the correct shell command is passed to it.
%x{…} is Ruby built-in syntax that will actually call Kernel method Backtick (`). So you can redefine that method. As backtick method returns the standard output of running cmd in a subshell, your redefined method should return something similar to that ,for example, a string.
module Kernel
def `(cmd)
"call #{cmd}"
end
end
puts %x(ls)
puts `ls`
# output
# call ls
# call ls
Using Mocha, if you want to mock to following class:
class Test
def method_under_test
system "echo 'Hello World!"
`ls -l`
end
end
your test would look something like:
def test_method_under_test
Test.any_instance.expects(:system).with("echo 'Hello World!'").returns('Hello World!').once
Test.any_instance.expects(:`).with("ls -l").once
end
This works because every object inherits methods like system and ` from the Kernel object.
I don't know of a way to mock a module, I'm afraid. With Mocha at least, Kernel.expects doesn't help. You could always wrap the calling in a class and mock that, something like this:
require 'test/unit'
require 'mocha'
class SystemCaller
def self.call(cmd)
system cmd
end
end
class TestMockingSystem < Test::Unit::TestCase
def test_mocked_out_system_call
SystemCaller.expects(:call).with('dir')
SystemCaller.call "dir"
end
end
which gives me what I'd hope for:
Started
.
Finished in 0.0 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
How about logging it to a text file, or outputting it to your console?
def log(file)
puts "git log #{file}"
end
Can't you just ovverride the function with a method that returns true when it gets the command?

Resources