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
allow(human).to receive(:play_on).with(any_args()).and_return(nil, :square)
– SteveTurczyn