2
votes

I'm trying to write a test to prevent you from doing the same thing twice (in this case, playing on the same square twice in tictactoe), but RSpec syntax is confusing me and I can't see a solution in their docs. I've tried a few approaches with no success, which have made me realise I don't understand RSpec syntax as well as I thought.

Because the tests are for a recursive method, a lot of the errors aren't as descriptive as I'd like since they cause the program to crash, which is making it hard to isolate the problem.

The method I'm trying to test/create will initially look something like this:

def your_turn

your_turn unless play_on(square_at(get_row, get_square))

end

(play_on returns nil if it doesn't successfully place a marker)

In this case I'm testing a module for which, your_turn, get_row and get_square are that module's methods, but play_on and square_at are methods from the class into which it's getting integrated, so I'm stubbing where necessary (ditto the get methods, since I don't want to get prompted by RSpec).

The first test I tried was this:

it 'repeats the process if a player can't play on that square' do

allow(human).to receive(:play_on).and_return(nil, :square)

expect(human).to receive(:your_turn).twice

human.your_turn

end

The test fails, and says it was only received once. The way I expected this to unfold was on the initial your_turn call, RSpec would stub play_on, return nil, setting off the unless statement and repeating the your_turn call - after which it would return a symbol and prevent any further recursion. What have I misunderstood?

After that effort, I came up with a monstrosity that seems to work, but I'm not entirely sure why:

allow(human).to receive(:get_row).and_return(1,2)

allow(human).to receive(:get_square).and_return(2,2)

allow(human).to receive(:square_at).with(1, 2).and_return(:nil)

allow(human).to receive(:square_at).with(2, 2).and_return(:square)

allow(human).to receive(:play_on).with(:square).and_return(:square)

expect(human).to receive(:play_on).twice

human.your_turn

The trouble here (other than this being ugly) is I got it to work more through brute force than understanding, and there's a couple of things I'm not sure of:

  • Why can I not remove eg the first two lines without the prompt returning to RSpec? Since on the third and fourth lines I'm stubbing the method square_at, which would call the get_ methods, why are they still being called?
  • Similarly, the fifth line third and fourth line seemed to cover the third and fourth, but I initially had the last 'allow' return(nil, :square), expecting that to work. That ended up calling your_turn three times where I was expecting two, so I removed the nil and it passed. The way I parse it, without the nil, play_on will return :square immediately, so the unless logic won't be triggered and the method will terminate. Again, what have I misunderstood?
  • Is there a dryer way of dealing with the two 'square_at' allowing lines?

Thanks all,

Sasha

1
In the first test (the one that failed) does it improve if you do...allow(human).to receive(:play_on).with(any_args()).and_return(nil, :square)SteveTurczyn
Sorry, premature return key...SteveTurczyn
Doesn't work - it still calls all three of the arguments within, so I get prompted twice and at the end, get a message saying 'undefined method `square_at' for #<HumanTest:0x007fb574fc8c68>'Arepo

1 Answers

0
votes

I don't understand your questions (specially the second and the third), but I believe that a big part of your problem is thinking that just because you stubed one method call, Ruby won't evaluate the method call arguments.

The get_row and get_square methods are called before square_at. Ruby "resolves" statements from "right to left", so it calls first get_row, then get_square and finally square_at passing the result of the two get_ methods. That could potentially break your tests depending on what those two methods do, and explains why you need the first two stubs.