0

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
rueeazy
  • 119
  • 1
  • 9
  • 1
    Check this out https://stackoverflow.com/questions/17258630/how-do-i-write-an-rspec-test-for-a-ruby-method-that-contains-gets-chomp – Yakov Jun 06 '20 at 13:27
  • 1
    @Yakov When linking another question or answer, it's often helpful to explain *why* it's relevant. The [answer](https://stackoverflow.com/q/17258630/1301972) to the question you pointed out is likely useful in fixing the OP's issue with IO#gets, but probably won't help with his current methods or test logic without other refactorings. – Todd A. Jacobs Jun 06 '20 at 15:58
  • 1
    `"NoMethodError: undefined method 'chomp' for nil:NilClass"` means either your instance method or mock are attempting to call String#chomp on an expression that's returning `nil`. While you could also stub out IO#gets, that's probably solving for the wrong problem here and creating additional test complexity. Below, I offer some suggestions for refactoring your Game#introduction method to make it more testable. – Todd A. Jacobs Jun 06 '20 at 16:06

1 Answers1

1

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.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199