我是RSpec的新手。我正在使用以下(正确的)规范代码来确保在测试控制器中调用方法find
和same_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
- 条款(我意识到,当我写这个问题时,我做了它并且它有效。)但我现在想要的是理解为什么我的使用间谍在这里不起作用。
我与间谍的失败尝试是:
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或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
答案 0 :(得分:1)
您的示例失败,因为您在设置期望之前在get :directors, :id=>@any_possible_id
块中调用before
。
与一些模拟框架不同,RSpec不会捕获在设置期望之前所做的调用。
describe "RSpec doubles" do
let(:foo){ instance_double('Foo') }
it "does not catch calls made before the expectation is set" do
foo.bar
expect(foo).to_not have_received(:bar)
end
it "catches calls made after the expectation is set" do
expect(foo).to have_received(:bar)
foo.bar
end
end
上面的两个例子都应该通过。
然而,RSpec确实以间谍为特色:
before(:each) do
# ...
allow(Movie).to receive(:find).and_return([@movie1,@movie2])
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("1")
end
此处的差异是allow(Movie).to receive(:find).and_return([@movie1,@movie2])
用间谍替换.find
类上的Movie
方法。
但是我认为你在这里对控制器内部进行了大量研究,而不是测试你的控制器如何完成它的工作,你应该测试它的行为。
这里相关的是你的控制器为给定的一组参数返回正确的结果集:
describe MoviesController do
describe 'GET #directors' do
let(:director) { FactoryGirl.create(:director) }
let!(:movies) do # not lazy loaded
[Movie.create(director: director), Movie.create(director: director), Movie.create]
end
before { get :directors, id: director.to_param }
it "matches movies by the given director" do
expect(assigns[:movies]).to include(movies.first)
expect(assigns[:movies].size).to eq 2
end
it "does not match movies which are not by the given director" do
expect(assigns[:movies]).to_not include(movies.last)
end
end
end
请注意,在控制器规范中,您通常会按describe "HTTPVERB #method"
对规范进行分组。它可以快速查找正在测试的内容。
在测试控制器时,有一些有效的模拟和间谍用途,但我想说是在测试应用程序的边界时 - 比如当你的应用程序触及第三方API时。另一个有效的案例是伪造身份验证/授权。
答案 1 :(得分:0)
这是一个相关问题:除了测试其行为之外,还可能需要确保调用某些控制器方法。例如,如果这些方法来自遗留代码,那么您需要通过自检来确保其他人不会来自控制器中替换自己的方法。 (无论如何,谢谢你,我很感激你的时间。)
这是回答问题的代码:
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
被定义为类Movie
中的spy。作为间谍,我们可以稍后在expect(@movie1).to have_received(:anything)
中使用它。如果将@movie1
定义为double
,则becomes necessary to use :allow
条款允许其接收方法anything
(或&#34;消息&#34 ;,作为文档的说法)。
第3行 - 此行是解释RSpec引发的错误的关键。 .and_return @movie1
是告诉RSpec @movie1
在控制器中扮演@movie
角色的基础(请参阅问题末尾的控制器代码)。 RSpec通过查看控制器中的Movie.find
返回@movie
来建立此匹配,如此allow
语句中所指定的那样。问题中出错的原因不是Movie
或@movie
没有在控制器中接收指定的方法;原因是RSpec未将规范中的Movie
和@movie1
与控制器中的真实Movie
和@movie
匹配。
其余的是不言自明的。顺便说一句,我带来了其他编写这个测试的方法,但这个使用间谍的方法是最紧凑的。