简体   繁体   中英

How do I write an Rspec test for a Ruby method that contains variable that contains 'gets.chomp'?

I've looked at other examples of tests, but most other examples don't necessarily have a variable that is equal to 'gets.chomp.downcase' and it's making it difficult for me to test.

The rest is for a chess game but I'm trying to make it so if you type in "new" in the introduction, it'll call the method #instructions, which displays the instructions and asks if you're ready to play.

Here's the method #introduction

def introduction
        puts " \n"
        print "     Welcome to chess! "
        puts "What would you like to do?"
        puts "

      * Start a new Game  ->  Enter 'new'
      * Load a saved Game ->  Enter 'load'

      * Exit              ->  Enter 'exit'"
      input = gets.chomp.downcase
      if input == "new"
        instructions
      elsif input == "load"
        load_game
      elsif input == "exit"
        exit!
      else 
        introduction
      end
    end

Here's the test that I have for it that keeps displaying the error "Failure/Error: input = gets.chomp.downcase"

"NoMethodError: undefined method `chomp' for nil:NilClass"

describe Game do
    describe "#introduction" do
        it "starts a new game with input 'new'" do

            io = StringIO.new
            io.puts "new"

            $stdin = io

            game = Game.new
            game.introduction
            allow(game).to receive(:gets).and_return(io.gets)

            expect(game).to receive(:instructions)
        end
    end
end

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM