6. subject
有⼈建議subject 要定義在⼀開始,才知道你現在是在測什麼主題
describe User do
# subject 有沒有給名字都可以
subject(:user) { User.new(first_name: 'Fred', last_name: 'Hung')}
it 'is valid' do
expect(user).to be_valid
end
# 可以直接改成
it { is_expected.to be_valid }
end
7. context
# 不好的寫法
it 'if user without first name it is invalid' do
end
⽤context 把情境抽出來
context 'when user without first name' do
it { is_epected.to be_invalid }
end
9. bad
describe '#type_id' do
before { @resource = FactoryBot.create :device }
before { @type = Type.find @resource.type_id }
it 'sets the type_id field' do
expect(@resource.type_id).to eq(@type.id)
end
end
10. good
describe '#type_id' do
let(:resource) { FactoryBot.create :device }
let(:type) { Type.find resource.type_id }
it 'sets the type_id field' do
expect(resource.type_id).to eq(type.id)
end
end
11. 搭配 context ,蹦出好滋味
describe User do
subject(:user) { User.new(first_name: first_name)}
let(:first_name) { 'Fred' }
context 'when user without first name' do
let(:first_name) { nil }
it { is_epected.to be_invalid }
end
end
14. around
⽤法: 例如Timecop
around { |example| Timecop.freeze(Time.zone.local(2020, 12, 9), &example) }
VCR
around(:each){ |example| VCR.use_cassette("adyen/doku_indomaret_payment_method"){ example.run } }
15. before / after
注意lifecycle
before :suite
before :context # 這就是 :all
before :example # 這就是 :each
after :example
after :context
after :suite
16. # hook 可以 nested
RSpec.describe "before and after callbacks" do
before(:context) do
puts "outer before context" # 1
end
example "in outer group" do
end
after(:context) do #4
puts "outer after context"
end
describe "nested group" do
before(:context) do
puts "inner before context" #2
end
example "in nested group" do
end
after(:context) do
puts "inner after context" #3
end
end
end
17. before(:all) 裡⾯不能⽤ let
before == before(:each) ,每次都會執⾏⼀遍,複層scope 也會依
序執⾏⼀遍
Rubocop RSpec 建議⼀個Group 只有⼀個before / after
20. be_*
# 只要物件本⾝可以⽀援 xxx?
# 例如 user.admin?
# 就可以⽤ be_xxx matcher
describe User do
subject { User.new(role: role) }
let(:role) { :normal}
context 'when user role is admin' do
let(:role) { :admin }
it { is_expected.to be_admin }
end
end
26. shared example
# spec/support/shared_examples/some_examples.rb
RSpec.shared_examples "some example" do |parameter|
let(:something) { parameter }
it "uses the given parameter" do
expect(something).to eq(parameter)
end
end
RSpec.describe SomeClass do
include_examples "some example", "parameter1"
# 或
it_behaves_like "some example", "parameter1"
end
29. # spec/support/helper/some_helper.rb
# note: 通常 在 spec_helper 會 require support 下的所有東⻄
module SomeHelper
extend ActiveSupport::Concern
class_method do
# 這裡就是 extend 啦
def say_class_method
puts "class_method!!!"
end
end
# 這裡就是 include 啦
def say_helper_method
puts "helper_method!!!"
end
end
RSpec.configure do |config|
config.include SomeHelper, :say_some_module
end
36. ⽤來時空旅⾏的,要注意 travel 和 freeze 的差別
describe "some set of tests to mock" do
before do
Timecop.freeze(Time.local(1990))
end
after do
Timecop.return
end
it "should do blah blah blah" do
end
end
44. 祕技:可以在⼀個測試中使⽤多筆錄製的mock
let(:payment_to_refund_cassettes) do
[
{ name: 'google_play_receipt_cancelled' },
{ name: 'refund_shopify_order' }
]
end
it 'refund payment that receipt is canncelled' do
VCR.use_cassettes(payment_to_refund_cassettes) do
expect { subject.perform }.to change { @payment.state }.from('active').to('cancelled')
end
end
46. 注意要放在spec helper 最上⾯
unless defined? SimpleCov
# Start SimpleCov if one is not using Spring
require 'simplecov'
SimpleCov.start 'rails'
end
跑完測試後可以去 your_repo/coverage/index.html 看
複雜的service 可以看說測試有沒有蓋到⼀些edge case