How do I test a function with gets.chomp in it?
You first separate the 2 concerns of the method:
def get_action
gets.chomp
end
def welcome_user
puts "Welcome to Jamaica and have a nice day!"
action = get_action
return "Required action was #{action}."
end
And then you test the second one separately.
require 'minitest/spec'
require 'minitest/autorun'
describe "Welcoming users" do
before do
def get_action; "test string" end
end
it "should work" do
welcome_user.must_equal "Required action was test string."
end
end
As for the first one, you can
- Test it by hand and rely that it won't break (recommended approach, TDD is not a religion).
- Get the subverted version of the shell in question and make it imitate the user, and compare
whetherget_action
indeed gets what the user types.
While this is a practical answer to your problem, I do not know how to do 2., I only know how to imitate the user behind the browser (watir-webdriver
) and not behind the shell session.
Testing program that runs with user input from the console
As demonstrated in this answer, you can override getvalue
to feed in the user input.
Here is complete code that works without actually using gets
. I had to add couple of missing methods - validate_floor_number
and final_destination
:
require 'minitest/autorun'
class Elevator
attr_accessor :current_floor
def initialize
@current_floor = 0
@requested_floor = 0
#@last_floor = false
end
def get_value
gets.chomp
end
def validate_floor_number(v)
v.to_i rescue false
end
def arrival
print "Enter floor number: "
@requested_floor = get_value
# only proceed if user entered an integer
if validate_floor_number(@requested_floor)
@requested_floor = @requested_floor.to_i
move
else
arrival
end
end
def move
msg = ""
@current_floor < @requested_floor ? msg = "Going Up!" : msg = "Going Down"
puts msg
@current_floor = @requested_floor
next_move
end
def final_destination
puts "Reached your floor"
end
def next_move
puts "Do you want to go to another floor? Y/N"
another_floor = (get_value).upcase
another_floor == 'N' ? final_destination : arrival
end
end
describe "checks that the elevator can change directions" do
before do
class Elevator
@@moves = [3, 'Y', 5, 'Y', 2, 'Y', 7, 'N'].each
def get_value; @@moves.next end
end
end
it "should stop on floor 7" do
e = Elevator.new
e.arrival
assert_equal(e.current_floor, 7)
end
end
Output of above program:
Run options: --seed 2561
# Running:
Enter floor number: Going Up!
Do you want to go to another floor? Y/N
Enter floor number: Going Up!
Do you want to go to another floor? Y/N
Enter floor number: Going Down
Do you want to go to another floor? Y/N
Enter floor number: Going Up!
Do you want to go to another floor? Y/N
Reached your floor
.
Finished in 0.001334s, 749.4982 runs/s, 749.4982 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
[Finished in 0.3s]
Testing gets in rspec (user input)
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
How do I write an Rspec test for a Ruby method that contains variable that contains 'gets.chomp'?
Use Code Injection Instead of Mocks or Stubs
You have multiple problems with your approach. I won't enumerate them all, but instead focus on three key mistakes:
- Unit tests should generally test method results, not replicate the internals.
- You're trying to use #allow without defining a double first.
- You seem to be trying to set a message expectation, instead of using a stub to return a value.
There are certainly other problems with your code and your tests, but that's where I'd start once you remove your reliance on #gets from within your test cases. For example, to test the various paths in your method, you should probably configure a series of tests for each expected value, where #and_return explicitly returns new
, load
, or whatever.
More pragmatically, you're most likely struggling because you wrote the code first, and now are trying to retrofit tests. While you could probably monkey-patch things to make it testable post-facto, you're probably better off refactoring your code to allow direct injection within your tests. For example:
def show_prompt
print prompt =<<~"EOF"
Welcome to chess! What would you like to do?
* Start a new Game -> Enter "new"
* Load a saved Game -> Enter "load"
* Exit -> Enter "exit"
Selection:\s
EOF
end
def introduction input=nil
show_prompt
# Use an injected input value, if present.
input ||= gets.chomp.downcase
case input
when "new" then instructions
when "load" then load_game
when "exit" then exit!
else introduction
end
end
This avoids the need to stub or mock an object in the first place. Your tests can now simply call #introduction with or without an explicit value. That allows you to spend your time testing your logic branches and method outputs, rather than writing a lot of scaffolding to support mocking of your IO#gets call or avoiding nil-related exceptions.
Check if a number entered by the user with gets.chomp is a float in ruby
puts "What are the first number you want to divide"
number1 = gets.chomp.to_i
=> 100
puts "What is the second number?"
number2 = gets.chomp.to_i
=> 3
# Regular math
result_a = number1 / number2
puts "#{number1} / #{number2} = #{result_a}"
=> 100 / 3 = 33 # Integer class persists...
# Use a ruby library instead! Numeric#divmod
result_b = number1.divmod(number2)
puts "Result: #{result_b}"
=> [33, 1] # [quotient, modulus]
How do I write an RSpec test for a Ruby method that contains gets.chomp ?
You can stub out standard input stream like this:
require 'stringio'
def capture_name
$stdin.gets.chomp
end
describe 'capture_name' do
before do
$stdin = StringIO.new("James T. Kirk\n")
end
after do
$stdin = STDIN
end
it "should be 'James T. Kirk'" do
expect(capture_name).to be == 'James T. Kirk'
end
end
How do i test user input from the gets method in ruby?
The reason you are struggling to test this method is because the method is doing too much. This method is collecting input from the user, creating a new object, adding the record to a list and saving it to disk. You have UI, Data Access and Object persistence all wrapped up in one method.
Try splitting the method into smaller methods and then test those individually. At the very least you could have 2 methods as follows :-
def new_guest(first_name, last_name, address, phone)
guest = Guest.new(first_name, last_name, address, phone)
@guests.push(new_guest)
File.open("guests", 'w') {|f| Marshal.dump(@guests, f) }
end
def get_guest_data()
printf "First name: "
first_name = gets.chomp
printf "Last name: "
last_name = gets.chomp
printf "Adress: "
adress = gets.chomp
printf "Phone:"
phone = gets.chomp
[ first_name, last_name, address, phone]
end
Then within your unit test you can test new_guest by passing in values generated within your test cases
Can you include `gets.chomp` in a variable with a prompt?
You could wrap them in a method:
def ask_for_input
prompt = "> "
puts prompt
gets.chomp
end
input = ask_for_input # both prints a prompt and reads input
Related Topics
Best Practices For Reusing Code Between Controllers in Ruby on Rails
Unresolved Specs During Gem::Specification.Reset:
How to Switch to Ruby 1.9.3 Installed Using Homebrew
Why Can't I Install Rails on Lion Using Rvm
Convert Time from One Time Zone to Another in Rails
Using Send_File to Download a File from Amazon S3
Gem Install Permission Problem
How to Use Activerecord in a Ruby Script Outside Rails
How to Wrap Link_To Around Some HTML Ruby Code
How to Use Bundler Behind a Proxy
Rails4 Unknown Encoding Name - Cp720
What Is the Use of Gemfile in Rails
How to Use Global Variables or Constant Values in Ruby
Find Unused Code in a Rails App
Rails Console: Reload! Not Reflecting Changes in Model Files? What Could Be Possible Reason
How to Call a Controller'S Method from a View (As We Call from Helper Ideally)