我倾向于使用before块来设置实例变量。然后在示例中使用这些变量。我最近偶然发现let()。根据RSpec文档,它被用来
... 定义一个记忆helper方法。该值将在同一个示例中的多个调用之间缓存,但不会在多个示例之间缓存。
这与在块之前使用实例变量有什么不同?以及什么时候应该使用let() vs before()?
我倾向于使用before块来设置实例变量。然后在示例中使用这些变量。我最近偶然发现let()。根据RSpec文档,它被用来
... 定义一个记忆helper方法。该值将在同一个示例中的多个调用之间缓存,但不会在多个示例之间缓存。
这与在块之前使用实例变量有什么不同?以及什么时候应该使用let() vs before()?
当前回答
重要的是要记住,let是惰性求值的,不要在其中放置副作用方法,否则您将无法轻松地从let更改到(:each)之前。 你可以用let!而不是let,以便在每个场景之前对其进行评估。
其他回答
“before”默认为before(:each)。参考Rspec Book,版权2010年,第228页。
before(scope = :each, options={}, &block)
我使用before(:each)为每个示例组播种一些数据,而不必调用let方法在“it”块中创建数据。在这种情况下,“it”块中的代码更少。
如果在某些例子中需要某些数据,而在其他例子中不需要,我会使用let。
before和let都可以很好地晾干“it”块。
为了避免混淆,“let”和前面的(:all)不一样。“Let”为每个示例(“it”)重新计算它的方法和值,但在同一个示例中跨多个调用缓存值。你可以在这里阅读更多信息:https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let
let是功能性的,因为它本质上是一个过程。
我马上就发现了一个陷阱……在正在评估变更的Spec块中。
let(:object) {FactoryGirl.create :object}
expect {
post :destroy, id: review.id
}.to change(Object, :count).by(-1)
您需要确保在期望块之外调用let。也就是说,你打电话给工厂女孩。在let块中创建。我通常通过验证对象是持久化的来做到这一点。
object.persisted?.should eq true
否则,当第一次调用let块时,由于延迟实例化,数据库将实际发生更改。
更新
只是加个注释。小心玩代码高尔夫球,或者在这种情况下,用这个答案的rspec高尔夫球。
在这种情况下,我只需要调用对象响应的某个方法。所以我调用_。persist ?_方法作为对象的真值。我要做的就是实例化对象。你可以称之为空?还是零?了。重点不在于测试本身,而在于通过调用来赋予对象生命。
所以你不能重构
object.persisted?.should eq true
是
object.should be_persisted
由于对象还没有被实例化…它的懒惰。:)
更新2
利用让!即时对象创建的语法,应该可以完全避免这个问题。注意,虽然它会挫败很多懒惰的目的,非撞让。
此外,在某些情况下,你可能实际上想要利用主题语法而不是let,因为它可能会给你额外的选项。
subject(:object) {FactoryGirl.create :object}
请Joseph注意——如果您在before(:all)中创建数据库对象,它们将不会在事务中被捕获,您更有可能在测试数据库中留下cruft。使用before(:each)代替。
使用let及其惰性求值的另一个原因是,你可以在上下文中重写let来测试一个复杂的对象,就像下面这个非常人为的例子:
context "foo" do
let(:params) do
{ :foo => foo, :bar => "bar" }
end
let(:foo) { "foo" }
it "is set to foo" do
params[:foo].should eq("foo")
end
context "when foo is bar" do
let(:foo) { "bar" }
# NOTE we didn't have to redefine params entirely!
it "is set to bar" do
params[:foo].should eq("bar")
end
end
end
我使用let来测试我的API规范中的HTTP 404响应,使用上下文。
为了创建资源,我使用let!但是为了存储资源标识符,我使用let。看看它是什么样子的:
let!(:country) { create(:country) }
let(:country_id) { country.id }
before { get "api/countries/#{country_id}" }
it 'responds with HTTP 200' { should respond_with(200) }
context 'when the country does not exist' do
let(:country_id) { -1 }
it 'responds with HTTP 404' { should respond_with(404) }
end
这样可以保持规范的干净和可读性。
使用实例变量和let()之间的区别在于let()是延迟求值的。这意味着let()直到它定义的方法第一次运行时才会被求值。
before和let之间的区别在于,let()提供了一种以“级联”风格定义一组变量的好方法。这样做可以简化代码,使规范看起来更好一些。