使用 Spies 检测方法调用

Use of Spies for detecting method calls

我是 RSpec 的新手。我正在使用以下(正确的)规范代码来确保在测试的控制器中调用方法 findsame_director

require 'rails_helper'

describe MoviesController do
    describe 'Searching movies for same director: ' do
        before :each do
            @movie1 = instance_double('Movie', id:1, title:'movie1')
            @movie2 = instance_double('Movie', id:2, title:'movie2')
            @any_possible_id = 1
        end
        it 'should call the model method that finds the movie corresponding to the passed id.' do
            allow(@movie1).to receive(:same_directors).and_return [@movie1,@movie2]
            expect(Movie).to receive(:find).with(@any_possible_id.to_s).and_return @movie1
            get :directors, :id=>@any_possible_id
        end
        it 'should call the model method that finds the movies corresponding to the same director.' do
            expect(@movie1).to receive(:same_directors).and_return [@movie1,@movie2]
            allow(Movie).to receive(:find).with(@any_possible_id.to_s).and_return @movie1
            get :directors, :id=>@any_possible_id
        end

这工作正常,但如您所见,两个 it 子句包含重复的 get :directors, :id=>@any_possible_id 行。鉴于控制器中语句的顺序(请参阅问题末尾的控制器代码),get 必须出现在 it 子句的末尾。挑战在于通过将其移至 before 子句来使其干燥。 当然,最好的解决办法是把它移到一个 after 子句中(我意识到当我写这个问题时,我做到了并且它起作用了。)但我现在想要的是理解为什么我使用此处的间谍无法正常工作。

我对 Spies 的(失败)尝试是:

describe MoviesController do
    describe 'Searching movies for same director: ' do
        before :each do
            @movie1 = instance_double('Movie', id:1, title:'movie1')
            @movie2 = instance_double('Movie', id:2, title:'movie2')
            @any_possible_id = 1
            @spyMovie = class_spy(Movie)
            @spymovie1 = instance_spy(Movie)
            get :directors, :id=>@any_possible_id
        end
        it 'should call the model method that finds the movie corresponding to the passed id.' do
            allow(@spymovie1).to receive(:same_directors)#.and_return [@movie1,@movie2]
            expect(@spyMovie).to have_received(:find).with(@any_possible_id.to_s)#.and_return @movie1
        end
        it 'should call the model method that finds the movies corresponding to the same director.' do
            expect(@spymovie1).to have_received(:same_directors)#.and_return [@movie1,@movie2]
            allow(@spyMovie).to receive(:find).with(@any_possible_id.to_s)#.and_return @movie1
        end

现在,第二段代码可能有几个问题,但我在 运行 rspec 时遇到的错误是:

  1) MoviesController Searching movies for same director:  should call the model method that finds the movie corresponding to the passed id.
     Failure/Error: expect(@spyMovie).to have_received(:find).with(@any_possible_id.to_s)#.and_return @movie1
       (ClassDouble(Movie) (anonymous)).find("1")
           expected: 1 time with arguments: ("1")
           received: 0 times
     # ./spec/controllers/movies_controller_spec.rb:32:in `block (3 levels) in <top (required)>'

  2) MoviesController Searching movies for same director:  should call the model method that finds the movies corresponding to the same director.
     Failure/Error: expect(@spymovie1).to have_received(:same_directors)#.and_return [@movie1,@movie2]
       (InstanceDouble(Movie) (anonymous)).same_directors(*(any args))
           expected: 1 time with any arguments
           received: 0 times with any arguments
     # ./spec/controllers/movies_controller_spec.rb:35:in `block (3 levels) in <top (required)>'

所以,你可以看到当我使用间谍时,没有检测到对 :find:same_directors 的调用。

我无法在 rspec.info nor in relishapp 中找到答案。如果您能提出建议,我将不胜感激。谢谢!

如果你需要它,这是控制器:

  def directors
    @movie = Movie.find(params[:id])
    @movies = @movie.same_directors
    if (@movies-[@movie]).empty?
      flash[:notice] = "'#{@movie.title}' has no director info"
      redirect_to movies_path
    end
  end

这是一个相关的问题:除了测试其行为外,您可能还想确保调用了某些控制器方法。例如,如果这些方法来自遗留代码,您希望通过自检测试确保其他人不会来替换控制器中她自己的方法。 (无论如何谢谢你,我感谢你的时间)。

这是回答问题的代码:

before :each do
    @movie1 = spy('Movie')
    allow(Movie).to receive(:find).and_return @movie1
    @any_possible_id = 1
    get :directors, :id=>@any_possible_id
end
it 'should call the model method that finds the movie corresponding to the passed id.' do
    expect(Movie).to have_received(:find).with(@any_possible_id.to_s)
end
it 'should call the model method that finds the movies corresponding to the same director.' do
    expect(@movie1).to have_received(:same_directors)
end

说明

第 2 行 - @movie1 被定义为一个 spy out of the class Movie. Being a spy, we can use it later in expect(@movie1).to have_received(:anything). If @movie1 is defined as a double then it becomes necessary to use 一个 :allow 子句,以允许它接收方法 anything(或 "message",作为文档的说法)。

第 3 行 - 这一行是解释 RSpec 抛出的错误 的关键。 .and_return @movie1 是告诉 RSpec @movie1 在控制器中扮演 @movie 角色的基础(请参阅问题末尾的控制器代码)。 RSpec 通过在控制器 returns @movie 中看到 Movie.find 来建立此匹配,如此 allow 语句中所指定。问题中错误的原因不是 Movie@movie 没有在控制器中接收到指定的方法;原因是 RSpec 与规范中的 Movie@movie1 与控制器中的真实 Movie@movie 不匹配。

其余部分不言自明。顺便说一句,我用其他方式编写这个测试,但是这个使用间谍的方式是最紧凑的。