rspec
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRSpec Testing Skill
RSpec测试技能指南
Expert guidance for writing comprehensive tests in RSpec for Ruby and Rails applications. This skill provides immediate, actionable testing strategies with deep-dive references for complex scenarios.
为Ruby和Rails应用编写全面RSpec测试的专业指南。本技能提供即时、可落地的测试策略,以及针对复杂场景的深度参考内容。
Quick Start
快速入门
Basic RSpec Structure
RSpec基础结构
ruby
undefinedruby
undefinedspec/models/user_spec.rb
spec/models/user_spec.rb
RSpec.describe User, type: :model do
describe '#full_name' do
it 'returns the first and last name' do
user = User.new(first_name: 'John', last_name: 'Doe')
expect(user.full_name).to eq('John Doe')
end
end
end
**Key concepts:**
- `describe`: Groups related tests (classes, methods)
- `context`: Describes specific scenarios
- `it`: Individual test example
- `expect`: Makes assertions using matchersRSpec.describe User, type: :model do
describe '#full_name' do
it 'returns the first and last name' do
user = User.new(first_name: 'John', last_name: 'Doe')
expect(user.full_name).to eq('John Doe')
end
end
end
**核心概念:**
- `describe`:对相关测试进行分组(类、方法)
- `context`:描述特定场景
- `it`:单个测试用例
- `expect`:使用匹配器进行断言Running Tests
运行测试
bash
undefinedbash
undefinedRun all specs
运行所有测试
bundle exec rspec
bundle exec rspec
Run specific file
运行指定文件
bundle exec rspec spec/models/user_spec.rb
bundle exec rspec spec/models/user_spec.rb
Run specific line
运行指定行的测试
bundle exec rspec spec/models/user_spec.rb:12
bundle exec rspec spec/models/user_spec.rb:12
Run with documentation format
以文档格式输出结果
bundle exec rspec --format documentation
bundle exec rspec --format documentation
Run only failures from last run
仅运行上次失败的测试
bundle exec rspec --only-failures
undefinedbundle exec rspec --only-failures
undefinedCore Testing Patterns
核心测试模式
1. Model Specs
1. 模型测试
Test business logic, validations, associations, and methods:
ruby
RSpec.describe Article, type: :model do
# Test validations
describe 'validations' do
it { should validate_presence_of(:title) }
it { should validate_length_of(:title).is_at_most(100) }
end
# Test associations
describe 'associations' do
it { should belong_to(:author) }
it { should have_many(:comments) }
end
# Test instance methods
describe '#published?' do
context 'when publish_date is in the past' do
it 'returns true' do
article = Article.new(publish_date: 1.day.ago)
expect(article.published?).to be true
end
end
context 'when publish_date is in the future' do
it 'returns false' do
article = Article.new(publish_date: 1.day.from_now)
expect(article.published?).to be false
end
end
end
# Test scopes
describe '.recent' do
it 'returns articles from the last 30 days' do
old = create(:article, created_at: 31.days.ago)
recent = create(:article, created_at: 1.day.ago)
expect(Article.recent).to include(recent)
expect(Article.recent).not_to include(old)
end
end
end测试业务逻辑、验证规则、关联关系和方法:
ruby
RSpec.describe Article, type: :model do
# 测试验证规则
describe 'validations' do
it { should validate_presence_of(:title) }
it { should validate_length_of(:title).is_at_most(100) }
end
# 测试关联关系
describe 'associations' do
it { should belong_to(:author) }
it { should have_many(:comments) }
end
# 测试实例方法
describe '#published?' do
context 'when publish_date is in the past' do
it 'returns true' do
article = Article.new(publish_date: 1.day.ago)
expect(article.published?).to be true
end
end
context 'when publish_date is in the future' do
it 'returns false' do
article = Article.new(publish_date: 1.day.from_now)
expect(article.published?).to be false
end
end
end
# 测试作用域
describe '.recent' do
it 'returns articles from the last 30 days' do
old = create(:article, created_at: 31.days.ago)
recent = create(:article, created_at: 1.day.ago)
expect(Article.recent).to include(recent)
expect(Article.recent).not_to include(old)
end
end
end2. Request Specs
2. 请求测试
Test HTTP requests and responses across the entire stack:
ruby
RSpec.describe 'Articles API', type: :request do
describe 'GET /articles' do
it 'returns all articles' do
create_list(:article, 3)
get '/articles'
expect(response).to have_http_status(:success)
expect(JSON.parse(response.body).size).to eq(3)
end
end
describe 'POST /articles' do
context 'with valid params' do
it 'creates a new article' do
article_params = { article: { title: 'New Article', body: 'Content' } }
expect {
post '/articles', params: article_params
}.to change(Article, :count).by(1)
expect(response).to have_http_status(:created)
end
end
context 'with invalid params' do
it 'returns errors' do
invalid_params = { article: { title: '' } }
post '/articles', params: invalid_params
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
describe 'authentication' do
it 'requires authentication for create' do
post '/articles', params: { article: { title: 'Test' } }
expect(response).to have_http_status(:unauthorized)
end
it 'allows authenticated users to create' do
user = create(:user)
post '/articles',
params: { article: { title: 'Test' } },
headers: { 'Authorization' => "Bearer #{user.token}" }
expect(response).to have_http_status(:created)
end
end
end测试整个技术栈中的HTTP请求与响应:
ruby
RSpec.describe 'Articles API', type: :request do
describe 'GET /articles' do
it 'returns all articles' do
create_list(:article, 3)
get '/articles'
expect(response).to have_http_status(:success)
expect(JSON.parse(response.body).size).to eq(3)
end
end
describe 'POST /articles' do
context 'with valid params' do
it 'creates a new article' do
article_params = { article: { title: 'New Article', body: 'Content' } }
expect {
post '/articles', params: article_params
}.to change(Article, :count).by(1)
expect(response).to have_http_status(:created)
end
end
context 'with invalid params' do
it 'returns errors' do
invalid_params = { article: { title: '' } }
post '/articles', params: invalid_params
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
describe 'authentication' do
it 'requires authentication for create' do
post '/articles', params: { article: { title: 'Test' } }
expect(response).to have_http_status(:unauthorized)
end
it 'allows authenticated users to create' do
user = create(:user)
post '/articles',
params: { article: { title: 'Test' } },
headers: { 'Authorization' => "Bearer #{user.token}" }
expect(response).to have_http_status(:created)
end
end
end3. System Specs (End-to-End)
3. 系统测试(端到端)
Test user workflows through the browser with Capybara:
ruby
RSpec.describe 'Article management', type: :system do
before { driven_by(:selenium_chrome_headless) }
scenario 'user creates an article' do
visit new_article_path
fill_in 'Title', with: 'My Article'
fill_in 'Body', with: 'Article content'
click_button 'Create Article'
expect(page).to have_content('Article was successfully created')
expect(page).to have_content('My Article')
end
scenario 'user edits an article' do
article = create(:article, title: 'Original Title')
visit article_path(article)
click_link 'Edit'
fill_in 'Title', with: 'Updated Title'
click_button 'Update Article'
expect(page).to have_content('Updated Title')
expect(page).not_to have_content('Original Title')
end
# Test JavaScript interactions
scenario 'user filters articles', js: true do
create(:article, title: 'Ruby Article', category: 'ruby')
create(:article, title: 'Python Article', category: 'python')
visit articles_path
select 'Ruby', from: 'filter'
expect(page).to have_content('Ruby Article')
expect(page).not_to have_content('Python Article')
end
end使用Capybara通过浏览器测试用户工作流:
ruby
RSpec.describe 'Article management', type: :system do
before { driven_by(:selenium_chrome_headless) }
scenario 'user creates an article' do
visit new_article_path
fill_in 'Title', with: 'My Article'
fill_in 'Body', with: 'Article content'
click_button 'Create Article'
expect(page).to have_content('Article was successfully created')
expect(page).to have_content('My Article')
end
scenario 'user edits an article' do
article = create(:article, title: 'Original Title')
visit article_path(article)
click_link 'Edit'
fill_in 'Title', with: 'Updated Title'
click_button 'Update Article'
expect(page).to have_content('Updated Title')
expect(page).not_to have_content('Original Title')
end
# 测试JavaScript交互
scenario 'user filters articles', js: true do
create(:article, title: 'Ruby Article', category: 'ruby')
create(:article, title: 'Python Article', category: 'python')
visit articles_path
select 'Ruby', from: 'filter'
expect(page).to have_content('Ruby Article')
expect(page).not_to have_content('Python Article')
end
endFactory Bot Integration
Factory Bot集成
Defining Factories
定义工厂
ruby
undefinedruby
undefinedspec/factories/users.rb
spec/factories/users.rb
FactoryBot.define do
factory :user do
first_name { 'John' }
last_name { 'Doe' }
sequence(:email) { |n| "user#{n}@example.com" }
password { 'password123' }
# Traits for variations
trait :admin do
role { 'admin' }
end
trait :with_articles do
transient do
articles_count { 3 }
end
after(:create) do |user, evaluator|
create_list(:article, evaluator.articles_count, author: user)
end
endend
factory :article do
sequence(:title) { |n| "Article #{n}" }
body { 'Article content' }
association :author, factory: :user
end
end
FactoryBot.define do
factory :user do
first_name { 'John' }
last_name { 'Doe' }
sequence(:email) { |n| "user#{n}@example.com" }
password { 'password123' }
# 用于变体的特征
trait :admin do
role { 'admin' }
end
trait :with_articles do
transient do
articles_count { 3 }
end
after(:create) do |user, evaluator|
create_list(:article, evaluator.articles_count, author: user)
end
endend
factory :article do
sequence(:title) { |n| "Article #{n}" }
body { 'Article content' }
association :author, factory: :user
end
end
Using factories
使用工厂
user = create(:user) # Persisted
user = build(:user) # Not persisted
admin = create(:user, :admin) # With trait
user = create(:user, :with_articles) # With association
users = create_list(:user, 5) # Multiple records
attributes = attributes_for(:user) # Hash of attributes
undefineduser = create(:user) # 已持久化
user = build(:user) # 未持久化
admin = create(:user, :admin) # 使用特征
user = create(:user, :with_articles) # 带关联
users = create_list(:user, 5) # 多条记录
attributes = attributes_for(:user) # 属性哈希
undefinedEssential Matchers
必备匹配器
Equality and Identity
相等性与同一性
ruby
expect(actual).to eq(expected) # ==
expect(actual).to eql(expected) # .eql?
expect(actual).to be(expected) # .equal?
expect(actual).to equal(expected) # same objectruby
expect(actual).to eq(expected) # ==
expect(actual).to eql(expected) # .eql?
expect(actual).to be(expected) # .equal?
expect(actual).to equal(expected) # 同一对象Truthiness and Types
真值与类型
ruby
expect(actual).to be_truthy # not nil or false
expect(actual).to be_falsy # nil or false
expect(actual).to be_nil
expect(actual).to be_a(Class)
expect(actual).to be_an_instance_of(Class)ruby
expect(actual).to be_truthy # 非nil或false
expect(actual).to be_falsy # nil或false
expect(actual).to be_nil
expect(actual).to be_a(Class)
expect(actual).to be_an_instance_of(Class)Collections
集合
ruby
expect(array).to include(item)
expect(array).to contain_exactly(1, 2, 3) # any order
expect(array).to match_array([1, 2, 3]) # any order
expect(array).to start_with(1, 2)
expect(array).to end_with(2, 3)ruby
expect(array).to include(item)
expect(array).to contain_exactly(1, 2, 3) # 任意顺序
expect(array).to match_array([1, 2, 3]) # 任意顺序
expect(array).to start_with(1, 2)
expect(array).to end_with(2, 3)Errors and Changes
错误与变更
ruby
expect { action }.to raise_error(ErrorClass)
expect { action }.to raise_error('message')
expect { action }.to change(User, :count).by(1)
expect { action }.to change { user.reload.name }.from('old').to('new')ruby
expect { action }.to raise_error(ErrorClass)
expect { action }.to raise_error('message')
expect { action }.to change(User, :count).by(1)
expect { action }.to change { user.reload.name }.from('old').to('new')Rails-Specific
Rails专属
ruby
expect(response).to have_http_status(:success)
expect(response).to have_http_status(200)
expect(response).to redirect_to(path)
expect { action }.to have_enqueued_job(JobClass)ruby
expect(response).to have_http_status(:success)
expect(response).to have_http_status(200)
expect(response).to redirect_to(path)
expect { action }.to have_enqueued_job(JobClass)Mocks, Stubs, and Doubles
模拟对象、存根与替身
Test Doubles
测试替身
ruby
undefinedruby
undefinedBasic double
基础替身
book = double('book', title: 'RSpec Book', pages: 300)
book = double('book', title: 'RSpec Book', pages: 300)
Verifying double (checks against real class)
验证式替身(检查真实类)
book = instance_double('Book', title: 'RSpec Book')
undefinedbook = instance_double('Book', title: 'RSpec Book')
undefinedStubbing Methods
方法存根
ruby
undefinedruby
undefinedOn test doubles
在测试替身上
allow(book).to receive(:title).and_return('New Title')
allow(book).to receive(:available?).and_return(true)
allow(book).to receive(:title).and_return('New Title')
allow(book).to receive(:available?).and_return(true)
On real objects
在真实对象上
user = User.new
allow(user).to receive(:admin?).and_return(true)
user = User.new
allow(user).to receive(:admin?).and_return(true)
Chaining
链式调用
allow(user).to receive_message_chain(:articles, :published).and_return([article])
undefinedallow(user).to receive_message_chain(:articles, :published).and_return([article])
undefinedMessage Expectations
消息期望
ruby
undefinedruby
undefinedExpect method to be called
期望方法被调用
expect(mailer).to receive(:deliver).and_return(true)
expect(mailer).to receive(:deliver).and_return(true)
With specific arguments
带特定参数
expect(service).to receive(:call).with(user, { notify: true })
expect(service).to receive(:call).with(user, { notify: true })
Number of times
调用次数
expect(logger).to receive(:info).once
expect(logger).to receive(:info).twice
expect(logger).to receive(:info).exactly(3).times
expect(logger).to receive(:info).at_least(:once)
undefinedexpect(logger).to receive(:info).once
expect(logger).to receive(:info).twice
expect(logger).to receive(:info).exactly(3).times
expect(logger).to receive(:info).at_least(:once)
undefinedSpies
间谍
ruby
undefinedruby
undefinedCreate spy
创建间谍
invitation = spy('invitation')
user.accept_invitation(invitation)
invitation = spy('invitation')
user.accept_invitation(invitation)
Verify after the fact
事后验证
expect(invitation).to have_received(:accept)
expect(invitation).to have_received(:accept).with(mailer)
undefinedexpect(invitation).to have_received(:accept)
expect(invitation).to have_received(:accept).with(mailer)
undefinedDRY Testing Techniques
测试代码复用技巧
Before Hooks
前置钩子
ruby
RSpec.describe ArticlesController do
before(:each) do
@user = create(:user)
sign_in @user
end
# OR using subject
subject { create(:article) }
it 'has a title' do
expect(subject.title).to be_present
end
endruby
RSpec.describe ArticlesController do
before(:each) do
@user = create(:user)
sign_in @user
end
# 或使用subject
subject { create(:article) }
it 'has a title' do
expect(subject.title).to be_present
end
endLet and Let
Let与Let!
ruby
describe Article do
let(:article) { create(:article) } # Lazy-loaded
let!(:published) { create(:article, :published) } # Eager-loaded
it 'can access article' do
expect(article).to be_valid
end
endruby
describe Article do
let(:article) { create(:article) } # 懒加载
let!(:published) { create(:article, :published) } # 预加载
it 'can access article' do
expect(article).to be_valid
end
endShared Examples
共享示例
ruby
undefinedruby
undefinedDefine shared examples
定义共享示例
RSpec.shared_examples 'a timestamped model' do
it 'has created_at' do
expect(subject).to respond_to(:created_at)
end
it 'has updated_at' do
expect(subject).to respond_to(:updated_at)
end
end
RSpec.shared_examples 'a timestamped model' do
it 'has created_at' do
expect(subject).to respond_to(:created_at)
end
it 'has updated_at' do
expect(subject).to respond_to(:updated_at)
end
end
Use shared examples
使用共享示例
describe Article do
it_behaves_like 'a timestamped model'
end
describe Comment do
it_behaves_like 'a timestamped model'
end
undefineddescribe Article do
it_behaves_like 'a timestamped model'
end
describe Comment do
it_behaves_like 'a timestamped model'
end
undefinedShared Contexts
共享上下文
ruby
RSpec.shared_context 'authenticated user' do
let(:current_user) { create(:user) }
before do
sign_in current_user
end
end
describe ArticlesController do
include_context 'authenticated user'
# Tests use current_user and are signed in
endruby
RSpec.shared_context 'authenticated user' do
let(:current_user) { create(:user) }
before do
sign_in current_user
end
end
describe ArticlesController do
include_context 'authenticated user'
# 测试使用current_user并已登录
endTDD Workflow
TDD工作流
Red-Green-Refactor Cycle
红-绿-重构循环
- Red: Write a failing test first
ruby
describe User do
it 'has a full name' do
user = User.new(first_name: 'John', last_name: 'Doe')
expect(user.full_name).to eq('John Doe')
end
end- 红:先编写失败的测试
ruby
describe User do
it 'has a full name' do
user = User.new(first_name: 'John', last_name: 'Doe')
expect(user.full_name).to eq('John Doe')
end
endFails: undefined method `full_name'
失败:undefined method `full_name'
2. **Green**: Write minimal code to pass
```ruby
class User
def full_name
"#{first_name} #{last_name}"
end
end
2. **绿**:编写最少代码使测试通过
```ruby
class User
def full_name
"#{first_name} #{last_name}"
end
endPasses!
通过!
3. **Refactor**: Improve code while keeping tests green
3. **重构**:在保持测试通过的同时优化代码Testing Strategy
测试策略
Start with system specs for user-facing features:
- Tests complete workflows
- Highest confidence
- Slowest to run
Drop to request specs for API/controller logic:
- Test HTTP interactions
- Faster than system specs
- Cover authentication, authorization, edge cases
Use model specs for business logic:
- Test calculations, validations, scopes
- Fast and focused
- Most of your test suite
从系统测试开始针对用户可见功能:
- 测试完整工作流
- 可信度最高
- 运行速度最慢
使用请求测试针对API/控制器逻辑:
- 测试HTTP交互
- 比系统测试快
- 覆盖认证、授权、边界场景
使用模型测试针对业务逻辑:
- 测试计算、验证、作用域
- 快速且聚焦
- 占测试套件的大部分
Configuration Best Practices
配置最佳实践
spec/rails_helper.rb
spec/rails_helper.rb
ruby
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
abort("Run in production!") if Rails.env.production?
require 'rspec/rails'ruby
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
abort("Run in production!") if Rails.env.production?
require 'rspec/rails'Auto-require support files
自动加载支持文件
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
RSpec.configure do |config|
Use transactional fixtures
config.use_transactional_fixtures = true
Infer spec type from file location
config.infer_spec_type_from_file_location!
Filter Rails backtrace
config.filter_rails_from_backtrace!
Include FactoryBot methods
config.include FactoryBot::Syntax::Methods
Include request helpers
config.include RequestHelpers, type: :request
Capybara configuration for system specs
config.before(:each, type: :system) do
driven_by :selenium_chrome_headless
end
end
undefinedDir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
RSpec.configure do |config|
使用事务性 fixtures
config.use_transactional_fixtures = true
从文件位置推断测试类型
config.infer_spec_type_from_file_location!
过滤Rails调用栈
config.filter_rails_from_backtrace!
包含FactoryBot方法
config.include FactoryBot::Syntax::Methods
包含请求助手
config.include RequestHelpers, type: :request
系统测试的Capybara配置
config.before(:each, type: :system) do
driven_by :selenium_chrome_headless
end
end
undefinedspec/spec_helper.rb
spec/spec_helper.rb
ruby
RSpec.configure do |config|
# Show detailed failure messages
config.example_status_persistence_file_path = "spec/examples.txt"
# Disable monkey patching (use expect syntax only)
config.disable_monkey_patching!
# Output warnings
config.warnings = true
# Profile slowest tests
config.profile_examples = 10 if ENV['PROFILE']
# Run specs in random order
config.order = :random
Kernel.srand config.seed
endruby
RSpec.configure do |config|
# 保存测试状态以便后续查看
config.example_status_persistence_file_path = "spec/examples.txt"
# 禁用猴子补丁(仅使用expect语法)
config.disable_monkey_patching!
# 输出警告
config.warnings = true
# 分析最慢的测试
config.profile_examples = 10 if ENV['PROFILE']
# 随机顺序运行测试
config.order = :random
Kernel.srand config.seed
endCommon Patterns
常见模式
Testing Background Jobs
测试后台任务
ruby
describe 'background jobs', type: :job do
it 'enqueues the job' do
expect {
SendEmailJob.perform_later(user)
}.to have_enqueued_job(SendEmailJob).with(user)
end
it 'performs the job' do
expect {
SendEmailJob.perform_now(user)
}.to change { ActionMailer::Base.deliveries.count }.by(1)
end
endruby
describe 'background jobs', type: :job do
it 'enqueues the job' do
expect {
SendEmailJob.perform_later(user)
}.to have_enqueued_job(SendEmailJob).with(user)
end
it 'performs the job' do
expect {
SendEmailJob.perform_now(user)
}.to change { ActionMailer::Base.deliveries.count }.by(1)
end
endTesting Mailers
测试邮件发送
ruby
describe UserMailer, type: :mailer do
describe '#welcome_email' do
let(:user) { create(:user) }
let(:mail) { UserMailer.welcome_email(user) }
it 'renders the subject' do
expect(mail.subject).to eq('Welcome!')
end
it 'renders the receiver email' do
expect(mail.to).to eq([user.email])
end
it 'renders the sender email' do
expect(mail.from).to eq(['noreply@example.com'])
end
it 'contains the user name' do
expect(mail.body.encoded).to include(user.name)
end
end
endruby
describe UserMailer, type: :mailer do
describe '#welcome_email' do
let(:user) { create(:user) }
let(:mail) { UserMailer.welcome_email(user) }
it 'renders the subject' do
expect(mail.subject).to eq('Welcome!')
end
it 'renders the receiver email' do
expect(mail.to).to eq([user.email])
end
it 'renders the sender email' do
expect(mail.from).to eq(['noreply@example.com'])
end
it 'contains the user name' do
expect(mail.body.encoded).to include(user.name)
end
end
endTesting File Uploads
测试文件上传
ruby
describe 'file upload', type: :system do
it 'allows user to upload avatar' do
user = create(:user)
sign_in user
visit edit_profile_path
attach_file 'Avatar', Rails.root.join('spec', 'fixtures', 'avatar.jpg')
click_button 'Update Profile'
expect(page).to have_content('Profile updated')
expect(user.reload.avatar).to be_attached
end
endruby
describe 'file upload', type: :system do
it 'allows user to upload avatar' do
user = create(:user)
sign_in user
visit edit_profile_path
attach_file 'Avatar', Rails.root.join('spec', 'fixtures', 'avatar.jpg')
click_button 'Update Profile'
expect(page).to have_content('Profile updated')
expect(user.reload.avatar).to be_attached
end
endPerformance Tips
性能优化技巧
-
Use let instead of before for lazy loading
-
Avoid database calls when testing logic (use mocks)
-
Use build instead of create when persistence isn't needed
-
Use build_stubbed for non-persisted objects with associations
-
Tag slow tests and exclude them during development:ruby
it 'slow test', :slow do # test code end # Run with: rspec --tag ~slow
-
使用let而非before实现懒加载
-
测试逻辑时避免数据库调用(使用模拟对象)
-
无需持久化时使用build而非create
-
使用build_stubbed创建带关联的非持久化对象
-
标记慢测试并在开发中排除:ruby
it 'slow test', :slow do # 测试代码 end # 运行命令:rspec --tag ~slow
When to Use Each Spec Type
各测试类型的适用场景
- Model specs: Business logic, calculations, validations, scopes
- Request specs: API endpoints, authentication, authorization, JSON responses
- System specs: User workflows, JavaScript interactions, form submissions
- Mailer specs: Email content, recipients, attachments
- Job specs: Background job enqueueing and execution
- Helper specs: View helper methods
- Routing specs: Custom routes (usually not needed)
- 模型测试:业务逻辑、计算、验证、作用域
- 请求测试:API端点、认证、授权、JSON响应
- 系统测试:用户工作流、JavaScript交互、表单提交
- 邮件测试:邮件内容、收件人、附件
- 任务测试:后台任务的入队与执行
- 助手测试:视图助手方法
- 路由测试:自定义路由(通常不需要)
Quick Reference
快速参考
Most Common Commands:
bash
rspec # Run all specs
rspec spec/models # Run model specs
rspec --tag ~slow # Exclude slow specs
rspec --only-failures # Rerun failures
rspec --format documentation # Readable output
rspec --profile # Show slowest specsMost Common Matchers:
- - value equality
eq(expected) - /
be_truthy- truthinessbe_falsy - - collection membership
include(item) - - exceptions
raise_error(Error) - - state changes
change { }.by(n)
Most Common Stubs:
- - stub method
allow(obj).to receive(:method) - - expect call
expect(obj).to receive(:method) - - create double
double('name', method: value)
最常用命令:
bash
rspec # 运行所有测试
rspec spec/models # 运行模型测试
rspec --tag ~slow # 排除慢测试
rspec --only-failures # 重跑失败测试
rspec --format documentation # 可读格式输出
rspec --profile # 显示最慢的测试最常用匹配器:
- - 值相等
eq(expected) - /
be_truthy- 真值判断be_falsy - - 集合成员
include(item) - - 异常捕获
raise_error(Error) - - 状态变更
change { }.by(n)
最常用存根:
- - 存根方法
allow(obj).to receive(:method) - - 期望调用
expect(obj).to receive(:method) - - 创建替身
double('name', method: value)
Reference Documentation
参考文档
For detailed information on specific topics, see the references directory:
- Core Concepts - Describe blocks, contexts, hooks, subject, let
- Matchers Guide - Complete matcher reference with examples
- Mocking and Stubbing - Test doubles, stubs, spies, message expectations
- Rails Testing - Rails-specific spec types and helpers
- Factory Bot - Test data strategies and patterns
- Best Practices - Testing philosophy, patterns, and anti-patterns
- Configuration - Setup, formatters, and optimization
如需特定主题的详细信息,请查看参考目录:
- 核心概念 - 描述块、上下文、钩子、subject、let
- 匹配器指南 - 完整匹配器参考及示例
- 模拟与存根 - 测试替身、存根、间谍、消息期望
- Rails测试 - Rails专属测试类型与助手
- Factory Bot - 测试数据策略与模式
- 最佳实践 - 测试理念、模式与反模式
- 配置 - 设置、格式化工具与优化
Common Scenarios
常见场景
Debugging Failing Tests
调试失败的测试
ruby
undefinedruby
undefinedUse save_and_open_page in system specs
在系统测试中使用save_and_open_page
scenario 'user creates article' do
visit new_article_path
save_and_open_page # Opens browser with current page state
...
end
scenario 'user creates article' do
visit new_article_path
save_and_open_page # 在浏览器中打开当前页面状态
...
end
Print response body in request specs
在请求测试中打印响应体
it 'creates article' do
post '/articles', params: { ... }
puts response.body # Debug API responses
expect(response).to be_successful
end
it 'creates article' do
post '/articles', params: { ... }
puts response.body # 调试API响应
expect(response).to be_successful
end
Use binding.pry for interactive debugging
使用binding.pry进行交互式调试
it 'calculates total' do
order = create(:order)
binding.pry # Pause execution here
expect(order.total).to eq(100)
end
undefinedit 'calculates total' do
order = create(:order)
binding.pry # 在此处暂停执行
expect(order.total).to eq(100)
end
undefinedTesting Complex Queries
测试复杂查询
ruby
describe '.search' do
let!(:ruby_article) { create(:article, title: 'Ruby Guide', body: 'Ruby content') }
let!(:rails_article) { create(:article, title: 'Rails Guide', body: 'Rails content') }
it 'finds articles by title' do
results = Article.search('Ruby')
expect(results).to include(ruby_article)
expect(results).not_to include(rails_article)
end
it 'finds articles by body' do
results = Article.search('Rails content')
expect(results).to include(rails_article)
end
endruby
describe '.search' do
let!(:ruby_article) { create(:article, title: 'Ruby Guide', body: 'Ruby content') }
let!(:rails_article) { create(:article, title: 'Rails Guide', body: 'Rails content') }
it 'finds articles by title' do
results = Article.search('Ruby')
expect(results).to include(ruby_article)
expect(results).not_to include(rails_article)
end
it 'finds articles by body' do
results = Article.search('Rails content')
expect(results).to include(rails_article)
end
endTesting Callbacks
测试回调
ruby
describe 'callbacks' do
describe 'after_create' do
it 'sends welcome email' do
expect(UserMailer).to receive(:welcome_email)
.with(an_instance_of(User))
.and_return(double(deliver_later: true))
create(:user)
end
end
describe 'before_save' do
it 'normalizes email' do
user = create(:user, email: 'USER@EXAMPLE.COM')
expect(user.email).to eq('user@example.com')
end
end
endThis skill provides comprehensive RSpec testing guidance. For specific scenarios or advanced techniques, refer to the detailed reference documentation in the directory.
references/ruby
describe 'callbacks' do
describe 'after_create' do
it 'sends welcome email' do
expect(UserMailer).to receive(:welcome_email)
.with(an_instance_of(User))
.and_return(double(deliver_later: true))
create(:user)
end
end
describe 'before_save' do
it 'normalizes email' do
user = create(:user, email: 'USER@EXAMPLE.COM')
expect(user.email).to eq('user@example.com')
end
end
end本技能提供全面的RSpec测试指南。如需特定场景或高级技术,请参考目录下的详细参考文档。
references/