I'm new to Unit Testing using RSpec and Ruby and I have a question on how to test if my code is using the gets method, but without prompting for user input.
Here is the code I'm trying to test. Nothing crazy here, just a simple one liner.
my_file.rb
My_name = gets
Here's my spec.
require 'stringio'
def capture_name
$stdin.gets.chomp
end
describe 'capture_name' do
before do
$stdin = StringIO.new("John Doe\n")
end
after do
$stdin = STDIN
end
it "should be 'John Doe'" do
expect(capture_name).to be == 'John Doe'
require_relative 'my_file.rb'
end
end
Now this spec works, but when I run the code it prompts for user input. I don't want it to do that. I want to simply test if the gets method is being called and possibly mock the user input. Not to sure how to achieve this in RSpec. In Python I would utilize unittest.mock is there a similar method in RSpec?
Thanks in advance!
Here's how you could stub gets with your return value.
require 'rspec'
RSpec.describe do
describe 'capture_name' do
it 'returns foo as input' do
allow($stdin).to receive(:gets).and_return('foo')
name = $stdin.gets
expect(name).to eq('food')
end
end
end
Failures:
1) should eq "food"
Failure/Error: expect(name).to eq('food')
expected: "food"
got: "foo"
(compared using ==)
To test if something is being called (such as a function) you can use expect($stdin).to receive(:gets).with('foo') to ensure it is being called (once) with the right args. The expectation line in this scenario has to go before name = $stdin.gets.
Related
My class has this #run method that so far is just this, to test the testing:
def run
puts "Enter 'class' to create a new class."
input = $stdin.gets.chomp
binding.pry
And in the tests so far I've got
allow($stdin).to receive(:gets).and_return 'class'
cli.run
Doing it this way I am able to see, in the pry session, that input has been set to 'class', as intended.
Is there a way to do with without adding $stdin to my call to gets in my method itself? i.e., input = gets.chomp
I've tried allow(cli.run).to receive(:gets).and_return 'class'
But then in the pry session, input is equal to the first line of the spec file!
You can avoid this as such:
def run
puts "Enter 'class' to create a new class."
input = gets.chomp
end
describe 'gets' do
it 'belongs to Kernel' do
allow_any_instance_of(Kernel).to receive(:gets).and_return('class')
expect(run).to eq('class')
end
end
The method gets actually belongs to the Kernel module. (method(:gets).owner == Kernel). Since Kernel is included in Object and almost all ruby objects inherit from Object this will work.
Now if run is an instance method scoped in a Class I would recommend scoping the stubbing a bit more such that:
class Test
def run
puts "Enter 'class' to create a new class."
input = gets.chomp
end
end
describe 'gets' do
it 'can be stubbed lower than that' do
allow_any_instance_of(Test).to receive(:gets).and_return('class')
expect(Test.new.run).to eq('class')
end
# or even
it 'or even lower than that' do
cli = Test.new
allow(cli).to receive(:gets).and_return('class')
expect(cli.run).to eq('class')
end
end
Example
I often build little single-purpose Ruby scripts like this:
#!/usr/bin/env ruby
class Widget
def end_data
DATA.read
end
def render_data source_data
source_data.upcase
end
end
w = Widget.new
puts w.render_data(w.end_data)
__END__
data set to work on.
I'd like to include RSpec tests directly inside the file while I'm working on it. Something like this (which doesn't work but illustrates what I'm trying to do):
#!/usr/bin/env ruby
class Widget
def end_data
DATA.read
end
def render_data source_data
source_data.upcase
end
def self_test
# This doesn't work but shows what I'm trying to
# accomplish. The goal is to have RSpec run these type
# of test when self_test is called.
describe "Widget" do
it "should render data properly" do
#w = Widget.new
expect(#w.render_data('test string')).to eq 'TEST STRING'
end
end
end
end
w = Widget.new
w.self_test
__END__
data set to work on.
I understand this is not the normal way to work with RSpec and isn't appropriate in most cases. That said, there are times when it would be nice. So, I'd like to know, is it possible?
There are two things. First off rspec by default won't pollute the global namespace with methods like describe and so on. The second thing is that you need to tell rspec to run the specs after they've been declared.
If you change your self_test method to be
RSpec.describe "Widget" do
it "should render data properly" do
#w = Widget.new
expect(#w.render_data('test string')).to eq 'TEST STRING'
end
end
RSpec::Core::Runner.invoke
(having of course done require 'rspec' then that will run your specs).
The invoke methods exits the process after running the specs. If you don't want to do that, or need more control over where output goes etc. you might want to drop down to the run method which allows you to control these things.
I'm trying to get my head around test-driven design, specifically RSpec. But I'm having trouble with some of the examples from The RSpec Book.
In the book, we test for output on $STDOUT like this:
output = double('output')
game = Game.new
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start()
Well, that works after a fashion. But why on earth should I care if the Game object uses the puts() method? If I change it to print(), should it really break the test? And, more importantly, isn't this against the one of the principals of TDD - that I should be testing what the method does (the design) rather than how it does it (the implementation)?
Is there some way I can write a test that just tests what ends up on $STDOUT, without looking at what method puts it there?
Create a display class with the ability to write the status out.
You production code will make use of this display object so you are free to change how you write to STDOUT. There will be just one place for this logic while your tests rely on the abstraction.
For example:
output = stub('output')
game = Game.new(output)
output.should_receive(:display).with('Welcome to Codebreaker!')
game.start()
While your production code will have something such as
class Output
def display(message)
# puts or whatever internally used here. You only need to change this here.
end
end
I'd make this test pass by doing the following:
def start
#output.display('Welcome to Codebreaker!')
end
Here the production code doesn't care how the output is displayed. It could be any form of display now the abstraction is in place.
All of the above theory is language agnostic, and works a treat. You still mock out things you don't own such as third party code, but you are still testing you are performing the job at hand via your abstraction.
take a look at this post. Nick raised questions about the same example, and a very interesting conversation follows in the comments. Hope you find it helpful.
Capture $stdout and test against that instead of trying to mock the various methods that might output to stdout. After all, you want to test stdout and not some convoluted method for mimicking it.
expect { some_code }.to match_stdout( 'some string' )
Which uses a custom Matcher (rspec 2)
RSpec::Matchers.define :match_stdout do |check|
#capture = nil
match do |block|
begin
stdout_saved = $stdout
$stdout = StringIO.new
block.call
ensure
#capture = $stdout
$stdout = stdout_saved
end
#capture.string.match check
end
failure_message_for_should do
"expected to #{description}"
end
failure_message_for_should_not do
"expected not to #{description}"
end
description do
"match [#{check}] on stdout [#{#capture.string}]"
end
end
RSpec 3 has changed the Matcher API slightly.
failure_message_for_should is now failure_message
failure_message_for_should_not is now failure_message_when_negated
supports_block_expectations? has been added to make errors clearer for blocks.
See Charles' answer for the complete rspec3 solution.
The way I'd test it is with a StringIO object. It acts like a file, but doesn't touch the filesystem. Apologies for the Test::Unit syntax - feel free to edit to RSpec syntax.
require "stringio"
output_file = StringIO.new
game = Game.new(output_file)
game.start
output_text = output_file.string
expected_text = "Welcome to Codebreaker!"
failure_message = "Doesn't include welcome message"
assert output_text.include?(expected_text), failure_message
I came across this blog post which helped me solve this issue:
Mocking standard output in rspec.
He sets up before/after blocks, and I ended up doing them inside the actual rspec itself, for some reason I couldn't get it to work from my spec_helper.rb as recommended.
Hope it helps!
An updated version of Matt's answer for RSpec 3.0:
RSpec::Matchers.define :match_stdout do |check|
#capture = nil
match do |block|
begin
stdout_saved = $stdout
$stdout = StringIO.new
block.call
ensure
#capture = $stdout
$stdout = stdout_saved
end
#capture.string.match check
end
failure_message do
"expected to #{description}"
end
failure_message_when_negated do
"expected not to #{description}"
end
description do
"match [#{check}] on stdout [#{#capture.string}]"
end
def supports_block_expectations?
true
end
end
I am trying to execute rspec code passed in to a Ruby process as a string. How can I execute rspec from within a Ruby process rather than from the commandline and capture the results?
# Ruby code
code = '
#solution
class User
def in_role?(role)
true
end
end
#Tests to pass
describe User do
it "should be in any roles assigned to it" do
user = User.new
user.should be_in_role("assigned role")
end
end
'
# spec code
# numExamples = examples
# numFailures = failures
Is there any reason you should get the code as a string in first place? If you want to have access to the data rspec produces, you can try using a custom RSpec formatter
I'm trying to make this test fail :)
it "should display the question" do
#ui.should_receive(:puts).with("What's your name?").once
#ui.ask_question("What's your name?")
end
At the moment it passes even if I don't call puts in my function.
Basically, #ui should call .puts on an object that probably defaults to $stdout. Then in your tests, you can replace $stdout with a StringIO object that you can set expectations on. This has the added benefit of making your #ui object more flexible.
Given the code:
require 'rubygems'
require 'spec'
class UI
def ask_question(q)
end
end
describe UI do
before do
#ui = UI.new
end
it "should display the question" do
#ui.should_receive(:puts).with("Whats your name?").once
#ui.ask_question("Whats your name?")
end
end
I get the failure:
F
1) Spec::Mocks::MockExpectationError in 'UI should display the question'
#<UI:0xb738effc> expected :puts with ("Whats your name?") once, but received it 0 times /home/avdi/tmp/puts_spec.rb:15:
Finished in 0.002575 seconds
1 example, 1 failure
What version of RSpec are you using?
You can try stringio or ZenTest, the following ruby-talk thread has more info.