使用instance_double测试#game_over?时调用#check_row失败
解决instance_double测试Game#game_over?方法时未检测到Board#check_row调用的问题
问题背景
尝试用instance_double测试Game类的#game_over?方法,该方法会调用Board类的#check_row,但测试失败。
测试代码
describe '#game_over' do subject(:game_over) { described_class.new } let(:board) { instance_double(Board) } context 'when #game_over is called' do it 'calls #check_row in Board' do game_over.game_over? expect(board).to receive(:check_row) #game_over.game_over? end end end
被测试的#game_over?方法
def game_over? return true if @board.check_row || @board.check_column || @board.check_diagonal || @board.check_antidiagonal false end
Game类初始化方法
def initialize @board = Board.new end
失败信息
1) Game#game_over when #game_over is called calls #check_row in Board Failure/Error: expect(board).to receive(:check_row) (InstanceDouble(Board) (anonymous)).check_row(*(any args)) expected: 1 time with any arguments received: 0 times with any arguments
问题原因
- 测试用的double对象未关联到Game实例:Game初始化时会自行创建一个新的Board实例,测试里定义的
board这个instance_double对象并没有被Game使用,两者完全独立。 - 方法调用与期望设置顺序错误:先调用了
game_over.game_over?,之后才设置expect(board).to receive(:check_row),此时方法已经执行完毕,不会触发后续的期望检测。
解决方案
步骤1:修改Game初始化方法,支持依赖注入
让Game的初始化可以接受一个可选的Board参数,这样测试时可以传入我们的double对象:
def initialize(board = Board.new) @board = board end
步骤2:调整测试代码,关联double对象并修正执行顺序
describe '#game_over?' do subject(:game) { described_class.new(board) } let(:board) { instance_double(Board) } context 'when called' do it 'calls #check_row on the board' do # 先设置期望 expect(board).to receive(:check_row) # 再调用被测试方法 game.game_over? end end end
额外说明
如果需要测试#game_over?的返回值逻辑,还可以给double对象的方法设置返回值,比如:
it 'returns true when check_row returns true' do allow(board).to receive(:check_row).and_return(true) allow(board).to receive(:check_column).and_return(false) allow(board).to receive(:check_diagonal).and_return(false) allow(board).to receive(:check_antidiagonal).and_return(false) expect(game.game_over?).to be(true) end
内容的提问来源于stack exchange,提问作者Ruiyot Abby
相关产品推荐
相关产品推荐

