How to Test a Function With Gets.Chomp in It

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

  1. Test it by hand and rely that it won't break (recommended approach, TDD is not a religion).
  2. Get the subverted version of the shell in question and make it imitate the user, and compare
    whether get_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:

  1. Unit tests should generally test method results, not replicate the internals.
  2. You're trying to use #allow without defining a double first.
  3. 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



Leave a reply



Submit