rails_claude_skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
- data/.github/ISSUE_TEMPLATE/config.yml +11 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
- data/.github/ISSUE_TEMPLATE/question.yml +90 -0
- data/.github/dependabot.yml +19 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/release.yml +66 -0
- data/.rubocop.yml +52 -0
- data/CHANGELOG.md +94 -0
- data/CLAUDE.md +332 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/CONTRIBUTING.md +580 -0
- data/LICENSE.txt +21 -0
- data/README.md +544 -0
- data/Rakefile +8 -0
- data/lib/generators/claude/agent/agent_generator.rb +71 -0
- data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
- data/lib/generators/claude/command/command_generator.rb +50 -0
- data/lib/generators/claude/command/templates/command.md.tt +28 -0
- data/lib/generators/claude/commands_library/create-pr.md +27 -0
- data/lib/generators/claude/commands_library/dbchange.md +19 -0
- data/lib/generators/claude/commands_library/quality.md +20 -0
- data/lib/generators/claude/commands_library/stimulus.md +19 -0
- data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
- data/lib/generators/claude/install/install_generator.rb +211 -0
- data/lib/generators/claude/install/templates/README.md.tt +59 -0
- data/lib/generators/claude/install/templates/USAGE +28 -0
- data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
- data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
- data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
- data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
- data/lib/generators/claude/rule/rule_generator.rb +175 -0
- data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
- data/lib/generators/claude/rules_library/code-style.md +37 -0
- data/lib/generators/claude/rules_library/database.md +47 -0
- data/lib/generators/claude/rules_library/hotwire.md +56 -0
- data/lib/generators/claude/rules_library/security.md +54 -0
- data/lib/generators/claude/rules_library/testing.md +47 -0
- data/lib/generators/claude/skill/skill_generator.rb +196 -0
- data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
- data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
- data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
- data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
- data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
- data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
- data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
- data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
- data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
- data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
- data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
- data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
- data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
- data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
- data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
- data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
- data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
- data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
- data/lib/generators/claude/views/views_generator.rb +113 -0
- data/lib/rails_claude_skills/railtie.rb +16 -0
- data/lib/rails_claude_skills/version.rb +5 -0
- data/lib/rails_claude_skills.rb +27 -0
- data/sig/rails_claude_skills.rbs +4 -0
- metadata +199 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rspec-testing
|
|
3
|
+
description: This skill should be used when writing, reviewing, or improving RSpec tests for Ruby on Rails applications. Use this skill for all testing tasks including model specs, controller specs, system specs, component specs, service specs, and integration tests. The skill provides comprehensive RSpec best practices from Better Specs and thoughtbot guides.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# RSpec Testing for Rails
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Write comprehensive, maintainable RSpec tests following industry best practices. This skill combines guidance from Better Specs and thoughtbot's testing guides to produce high-quality test coverage for Rails applications.
|
|
11
|
+
|
|
12
|
+
## Core Testing Principles
|
|
13
|
+
|
|
14
|
+
### 1. Test-Driven Development (TDD)
|
|
15
|
+
Follow the Red-Green-Refactor cycle:
|
|
16
|
+
- **Red**: Write failing tests that define expected behavior
|
|
17
|
+
- **Green**: Implement minimal code to make tests pass
|
|
18
|
+
- **Refactor**: Improve code while tests continue to pass
|
|
19
|
+
|
|
20
|
+
### 2. Test Structure (Arrange-Act-Assert)
|
|
21
|
+
Organize tests with clear phases separated by newlines:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
it 'creates a new article' do
|
|
25
|
+
# Arrange - set up test data
|
|
26
|
+
user = create(:user)
|
|
27
|
+
attributes = {title: 'Test Article', body: 'Content here'}
|
|
28
|
+
|
|
29
|
+
# Act - perform the action
|
|
30
|
+
article = Article.create(attributes)
|
|
31
|
+
|
|
32
|
+
# Assert - verify the outcome
|
|
33
|
+
expect(article).to be_persisted
|
|
34
|
+
expect(article.title).to eq('Test Article')
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 3. Single Responsibility
|
|
39
|
+
Each test should verify one behavior. For unit tests, use one expectation per test. For integration tests, multiple expectations are acceptable when testing a complete flow.
|
|
40
|
+
|
|
41
|
+
### 4. Test Real Behavior
|
|
42
|
+
Avoid over-mocking. Test actual application behavior when possible. Only stub external services, slow operations, and dependencies outside your control.
|
|
43
|
+
|
|
44
|
+
## Test Type Decision Tree
|
|
45
|
+
|
|
46
|
+
### When to Write Model Specs
|
|
47
|
+
Use model specs (`spec/models/`) for:
|
|
48
|
+
- Validations
|
|
49
|
+
- Associations
|
|
50
|
+
- Scopes
|
|
51
|
+
- Instance methods
|
|
52
|
+
- Class methods
|
|
53
|
+
- Enums and constants
|
|
54
|
+
- Database constraints
|
|
55
|
+
|
|
56
|
+
**Example:**
|
|
57
|
+
```ruby
|
|
58
|
+
# spec/models/article_spec.rb
|
|
59
|
+
RSpec.describe Article do
|
|
60
|
+
describe 'validations' do
|
|
61
|
+
it 'validates presence of title' do
|
|
62
|
+
article = build(:article, title: nil)
|
|
63
|
+
expect(article).not_to be_valid
|
|
64
|
+
expect(article.errors[:title]).to include("can't be blank")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe 'associations' do
|
|
69
|
+
it { is_expected.to belong_to(:user) }
|
|
70
|
+
it { is_expected.to have_many(:comments) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#published?' do
|
|
74
|
+
it 'returns true when status is published' do
|
|
75
|
+
article = build(:article, status: :published)
|
|
76
|
+
expect(article.published?).to be true
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### When to Write Controller Specs
|
|
83
|
+
Use controller specs (`spec/controllers/`) for:
|
|
84
|
+
- Authorization checks (Pundit/CanCanCan)
|
|
85
|
+
- Request routing and parameter handling
|
|
86
|
+
- Response status codes
|
|
87
|
+
- Instance variable assignments
|
|
88
|
+
- Flash messages
|
|
89
|
+
- Redirects
|
|
90
|
+
|
|
91
|
+
**Example:**
|
|
92
|
+
```ruby
|
|
93
|
+
# spec/controllers/articles_controller_spec.rb
|
|
94
|
+
RSpec.describe ArticlesController do
|
|
95
|
+
describe 'POST #create' do
|
|
96
|
+
context 'with valid parameters' do
|
|
97
|
+
it 'creates a new article and redirects' do
|
|
98
|
+
user = create(:user)
|
|
99
|
+
session[:user_id] = user.id
|
|
100
|
+
|
|
101
|
+
valid_attributes = {
|
|
102
|
+
title: 'Test Article',
|
|
103
|
+
body: 'Article content'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
expect do
|
|
107
|
+
post :create, params: {article: valid_attributes}
|
|
108
|
+
end.to change(Article, :count).by(1)
|
|
109
|
+
|
|
110
|
+
expect(response).to redirect_to(Article.last)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context 'with invalid parameters' do
|
|
115
|
+
it 'does not create article and renders new template' do
|
|
116
|
+
user = create(:user)
|
|
117
|
+
session[:user_id] = user.id
|
|
118
|
+
|
|
119
|
+
invalid_attributes = {title: '', body: ''}
|
|
120
|
+
|
|
121
|
+
expect do
|
|
122
|
+
post :create, params: {article: invalid_attributes}
|
|
123
|
+
end.not_to change(Article, :count)
|
|
124
|
+
|
|
125
|
+
expect(response).to render_template(:new)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### When to Write System Specs
|
|
133
|
+
Use system specs (`spec/system/`) for:
|
|
134
|
+
- End-to-end user workflows
|
|
135
|
+
- Multi-step interactions
|
|
136
|
+
- JavaScript functionality
|
|
137
|
+
- Form submissions
|
|
138
|
+
- Navigation flows
|
|
139
|
+
- Real user scenarios
|
|
140
|
+
|
|
141
|
+
**Naming convention:** `user_action_spec.rb` or `feature_description_spec.rb`
|
|
142
|
+
|
|
143
|
+
**Example:**
|
|
144
|
+
```ruby
|
|
145
|
+
# spec/system/article_creation_spec.rb
|
|
146
|
+
RSpec.describe 'Article Creation' do
|
|
147
|
+
it 'allows a user to create a new article' do
|
|
148
|
+
user = create(:user)
|
|
149
|
+
|
|
150
|
+
# Sign in
|
|
151
|
+
visit '/login'
|
|
152
|
+
fill_in 'Email', with: user.email
|
|
153
|
+
fill_in 'Password', with: 'password'
|
|
154
|
+
click_button 'Sign In'
|
|
155
|
+
|
|
156
|
+
# Navigate to new article page
|
|
157
|
+
click_link 'New Article'
|
|
158
|
+
expect(page).to have_current_path(new_article_path)
|
|
159
|
+
|
|
160
|
+
# Fill out the article form
|
|
161
|
+
fill_in 'Title', with: 'My Test Article'
|
|
162
|
+
fill_in 'Body', with: 'This is the article content'
|
|
163
|
+
select 'Published', from: 'Status'
|
|
164
|
+
|
|
165
|
+
# Submit the form
|
|
166
|
+
click_button 'Create Article'
|
|
167
|
+
|
|
168
|
+
expect(page).to have_content('Article created successfully!')
|
|
169
|
+
expect(page).to have_content('My Test Article')
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### When to Write Component Specs
|
|
175
|
+
Use component specs (`spec/components/`) for:
|
|
176
|
+
- ViewComponent rendering
|
|
177
|
+
- Variant behavior
|
|
178
|
+
- Slot functionality
|
|
179
|
+
- Conditional rendering
|
|
180
|
+
- Component attributes
|
|
181
|
+
|
|
182
|
+
**Example:**
|
|
183
|
+
```ruby
|
|
184
|
+
# spec/components/button_component_spec.rb
|
|
185
|
+
RSpec.describe ButtonComponent, type: :component do
|
|
186
|
+
describe 'variants' do
|
|
187
|
+
it 'renders primary variant' do
|
|
188
|
+
render_inline(described_class.new(variant: :primary)) { 'Click me' }
|
|
189
|
+
|
|
190
|
+
button = page.find('button')
|
|
191
|
+
expect(button[:class]).to include('btn-primary')
|
|
192
|
+
expect(page).to have_button('Click me')
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'renders secondary variant' do
|
|
196
|
+
render_inline(described_class.new(variant: :secondary)) { 'Cancel' }
|
|
197
|
+
|
|
198
|
+
button = page.find('button')
|
|
199
|
+
expect(button[:class]).to include('btn-secondary')
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### When to Write Service/Integration Specs
|
|
206
|
+
Use service/integration specs (`spec/services/`, `spec/integration/`) for:
|
|
207
|
+
- Complex business logic
|
|
208
|
+
- Multi-step workflows
|
|
209
|
+
- External API integrations
|
|
210
|
+
- Background job processing
|
|
211
|
+
- Data transformations
|
|
212
|
+
|
|
213
|
+
## RSpec Syntax & Style Guide
|
|
214
|
+
|
|
215
|
+
### Describe Blocks
|
|
216
|
+
Use Ruby documentation conventions:
|
|
217
|
+
- `.method_name` for class methods
|
|
218
|
+
- `#method_name` for instance methods
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
describe '.find_by_title' do # class method
|
|
222
|
+
describe '#publish' do # instance method
|
|
223
|
+
describe 'validations' do # grouping
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Context Blocks
|
|
227
|
+
Start with "when," "with," or "without":
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
context 'when user is admin' do
|
|
231
|
+
context 'with valid parameters' do
|
|
232
|
+
context 'without authentication' do
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### It Blocks
|
|
236
|
+
- Keep descriptions under 40 characters
|
|
237
|
+
- Use third-person present tense
|
|
238
|
+
- **Never** use "should" in descriptions
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
# ✅ Good
|
|
242
|
+
it 'creates a new article' do
|
|
243
|
+
it 'validates presence of title' do
|
|
244
|
+
it 'redirects to dashboard' do
|
|
245
|
+
|
|
246
|
+
# ❌ Bad
|
|
247
|
+
it 'should create a new article' do
|
|
248
|
+
it 'should validate presence of title' do
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Expectations
|
|
252
|
+
Always use `expect` syntax (never `should`):
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
# ✅ Good
|
|
256
|
+
expect(article).to be_valid
|
|
257
|
+
expect(response).to have_http_status(:success)
|
|
258
|
+
expect { action }.to change(Article, :count).by(1)
|
|
259
|
+
|
|
260
|
+
# ❌ Bad (deprecated)
|
|
261
|
+
article.should be_valid
|
|
262
|
+
response.should have_http_status(:success)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### One-Liners
|
|
266
|
+
Use `is_expected` for concise one-line specs:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
subject { article }
|
|
270
|
+
|
|
271
|
+
it { is_expected.to be_valid }
|
|
272
|
+
it { is_expected.to be_persisted }
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## System Test Best Practices
|
|
276
|
+
|
|
277
|
+
### Authentication in System Tests
|
|
278
|
+
|
|
279
|
+
Test authentication flows directly without stubbing:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
# Good - test the actual login flow
|
|
283
|
+
visit '/login'
|
|
284
|
+
fill_in 'Email', with: user.email
|
|
285
|
+
fill_in 'Password', with: 'password'
|
|
286
|
+
click_button 'Sign In'
|
|
287
|
+
|
|
288
|
+
expect(page).to have_content('Dashboard')
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Controller Test Authentication
|
|
292
|
+
|
|
293
|
+
For controller tests, use direct session assignment rather than stubbing:
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
# ✅ Good - direct session assignment
|
|
297
|
+
session[:user_id] = user.id
|
|
298
|
+
|
|
299
|
+
# ❌ Avoid - stubbing authentication
|
|
300
|
+
allow_any_instance_of(Controller).to receive(:logged_in?).and_return(true)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Avoid CSS Class Testing
|
|
304
|
+
|
|
305
|
+
Don't test implementation details like CSS utility classes. Test semantic selectors and content:
|
|
306
|
+
|
|
307
|
+
```ruby
|
|
308
|
+
# ✅ Good - semantic selectors
|
|
309
|
+
expect(page).to have_selector(:test_id, 'user-modal')
|
|
310
|
+
expect(page).to have_css("[aria-hidden='false']")
|
|
311
|
+
expect(page).to have_content('Success message')
|
|
312
|
+
expect(page).to have_button('Submit')
|
|
313
|
+
|
|
314
|
+
# ❌ Bad - coupling to CSS implementation
|
|
315
|
+
expect(page).to have_css('.opacity-100')
|
|
316
|
+
expect(page).to have_css('.bg-red-500')
|
|
317
|
+
expect(page).to have_css('.rounded-lg')
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Factory Patterns
|
|
321
|
+
|
|
322
|
+
### Organization
|
|
323
|
+
1. Associations (implicit) first
|
|
324
|
+
2. Attributes (alphabetical)
|
|
325
|
+
3. Traits (alphabetical)
|
|
326
|
+
|
|
327
|
+
```ruby
|
|
328
|
+
FactoryBot.define do
|
|
329
|
+
factory :article do
|
|
330
|
+
# Associations
|
|
331
|
+
user
|
|
332
|
+
category
|
|
333
|
+
|
|
334
|
+
# Attributes (alphabetical)
|
|
335
|
+
body { 'Article content goes here...' }
|
|
336
|
+
published_at { Time.current }
|
|
337
|
+
status { :draft }
|
|
338
|
+
title { 'Sample Article Title' }
|
|
339
|
+
|
|
340
|
+
# Traits (alphabetical)
|
|
341
|
+
trait :published do
|
|
342
|
+
status { :published }
|
|
343
|
+
published_at { 1.day.ago }
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
trait :with_tags do
|
|
347
|
+
after(:create) do |article|
|
|
348
|
+
create_list(:tag, 3, article: article)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Prefer Build Over Create
|
|
356
|
+
Use `build` and `build_stubbed` when database persistence isn't needed:
|
|
357
|
+
|
|
358
|
+
```ruby
|
|
359
|
+
# ✅ Good - fast, no database hit
|
|
360
|
+
it 'validates title format' do
|
|
361
|
+
article = build(:article, title: '')
|
|
362
|
+
expect(article).not_to be_valid
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Less optimal - unnecessary database hit
|
|
366
|
+
it 'validates title format' do
|
|
367
|
+
article = create(:article, title: '')
|
|
368
|
+
expect(article).not_to be_valid
|
|
369
|
+
end
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Common Testing Patterns
|
|
373
|
+
|
|
374
|
+
### Testing Validations
|
|
375
|
+
```ruby
|
|
376
|
+
describe 'validations' do
|
|
377
|
+
it 'validates presence of title' do
|
|
378
|
+
article = build(:article, title: nil)
|
|
379
|
+
expect(article).not_to be_valid
|
|
380
|
+
expect(article.errors[:title]).to include("can't be blank")
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
it 'validates length of title' do
|
|
384
|
+
article = build(:article, title: 'a' * 256)
|
|
385
|
+
expect(article).not_to be_valid
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
it 'allows valid titles' do
|
|
389
|
+
article = build(:article, title: 'Valid Title')
|
|
390
|
+
expect(article).to be_valid
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Testing Enums
|
|
396
|
+
```ruby
|
|
397
|
+
describe 'enums' do
|
|
398
|
+
it 'defines status enum' do
|
|
399
|
+
expect(described_class.statuses).to eq({
|
|
400
|
+
'draft' => 'draft',
|
|
401
|
+
'published' => 'published',
|
|
402
|
+
'archived' => 'archived'
|
|
403
|
+
})
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
it 'has correct default' do
|
|
407
|
+
article = described_class.new
|
|
408
|
+
expect(article.status).to eq('draft')
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Testing Authorization
|
|
414
|
+
```ruby
|
|
415
|
+
context 'when user is not admin' do
|
|
416
|
+
it 'raises authorization error' do
|
|
417
|
+
user = create(:user, role: :member)
|
|
418
|
+
session[:user_id] = user.id
|
|
419
|
+
|
|
420
|
+
expect do
|
|
421
|
+
get :admin_dashboard
|
|
422
|
+
end.to raise_error(Pundit::NotAuthorizedError)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Using Shoulda Matchers
|
|
428
|
+
```ruby
|
|
429
|
+
describe 'associations' do
|
|
430
|
+
it { is_expected.to belong_to(:user) }
|
|
431
|
+
it { is_expected.to have_many(:comments) }
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
describe 'validations' do
|
|
435
|
+
it { is_expected.to validate_presence_of(:title) }
|
|
436
|
+
it { is_expected.to validate_length_of(:title).is_at_most(255) }
|
|
437
|
+
end
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## What to Avoid
|
|
441
|
+
|
|
442
|
+
### ❌ Don't Stub the System Under Test
|
|
443
|
+
Never mock or stub methods on the class being tested:
|
|
444
|
+
|
|
445
|
+
```ruby
|
|
446
|
+
# ❌ Bad
|
|
447
|
+
it 'processes payment' do
|
|
448
|
+
order = Order.new
|
|
449
|
+
allow(order).to receive(:calculate_total).and_return(100)
|
|
450
|
+
expect(order.process_payment).to be true
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# ✅ Good
|
|
454
|
+
it 'processes payment' do
|
|
455
|
+
order = Order.new(line_items: [line_item])
|
|
456
|
+
expect(order.process_payment).to be true
|
|
457
|
+
end
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### ❌ Don't Test Private Methods
|
|
461
|
+
Test the public interface. Private methods are tested indirectly:
|
|
462
|
+
|
|
463
|
+
```ruby
|
|
464
|
+
# ❌ Bad
|
|
465
|
+
describe '#calculate_total (private)' do
|
|
466
|
+
it 'sums line items' do
|
|
467
|
+
order.send(:calculate_total)
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# ✅ Good
|
|
472
|
+
describe '#total' do
|
|
473
|
+
it 'returns sum of line items' do
|
|
474
|
+
expect(order.total).to eq(100)
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### ❌ Avoid `any_instance_of`
|
|
480
|
+
Use dependency injection instead:
|
|
481
|
+
|
|
482
|
+
```ruby
|
|
483
|
+
# ❌ Bad
|
|
484
|
+
allow_any_instance_of(PaymentService).to receive(:charge)
|
|
485
|
+
|
|
486
|
+
# ✅ Good
|
|
487
|
+
payment_service = instance_double(PaymentService)
|
|
488
|
+
allow(payment_service).to receive(:charge).and_return(success)
|
|
489
|
+
order = Order.new(payment_service: payment_service)
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
## Quick Reference
|
|
493
|
+
|
|
494
|
+
### Test Organization
|
|
495
|
+
```ruby
|
|
496
|
+
RSpec.describe ClassName do
|
|
497
|
+
# Setup (let, before)
|
|
498
|
+
let(:resource) { create(:resource) }
|
|
499
|
+
|
|
500
|
+
before do
|
|
501
|
+
# common setup
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Validations
|
|
505
|
+
describe 'validations' do
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# Associations
|
|
509
|
+
describe 'associations' do
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# Class methods
|
|
513
|
+
describe '.class_method' do
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Instance methods
|
|
517
|
+
describe '#instance_method' do
|
|
518
|
+
context 'when condition' do
|
|
519
|
+
it 'does something' do
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Expectation Matchers
|
|
527
|
+
```ruby
|
|
528
|
+
# Equality
|
|
529
|
+
expect(value).to eq(expected)
|
|
530
|
+
expect(value).to be(expected) # same object
|
|
531
|
+
expect(value).to match(/regex/)
|
|
532
|
+
|
|
533
|
+
# Predicates
|
|
534
|
+
expect(object).to be_valid
|
|
535
|
+
expect(object).to be_persisted
|
|
536
|
+
expect(collection).to be_empty
|
|
537
|
+
|
|
538
|
+
# Collections
|
|
539
|
+
expect(array).to include(item)
|
|
540
|
+
expect(array).to contain_exactly(1, 2, 3)
|
|
541
|
+
expect(hash).to have_key(:name)
|
|
542
|
+
|
|
543
|
+
# Changes
|
|
544
|
+
expect { action }.to change(Model, :count).by(1)
|
|
545
|
+
expect { action }.to change { object.attribute }.from(old).to(new)
|
|
546
|
+
|
|
547
|
+
# Errors
|
|
548
|
+
expect { action }.to raise_error(ErrorClass)
|
|
549
|
+
expect { action }.not_to raise_error
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Resources
|
|
553
|
+
|
|
554
|
+
This skill includes detailed reference documentation in the `references/` directory:
|
|
555
|
+
|
|
556
|
+
### `references/better_specs_guide.md`
|
|
557
|
+
Comprehensive patterns from Better Specs including:
|
|
558
|
+
- Describe/context/it block conventions
|
|
559
|
+
- Subject and let usage
|
|
560
|
+
- Mocking strategies
|
|
561
|
+
- Shared examples
|
|
562
|
+
- Factory patterns
|
|
563
|
+
|
|
564
|
+
### `references/thoughtbot_patterns.md`
|
|
565
|
+
thoughtbot's RSpec best practices covering:
|
|
566
|
+
- Modern RSpec syntax
|
|
567
|
+
- Test structure and organization
|
|
568
|
+
- What to avoid in tests
|
|
569
|
+
- Capybara patterns for system tests
|
|
570
|
+
- Factory organization
|
|
571
|
+
|
|
572
|
+
Load these references when you need detailed examples or are unsure about a specific pattern.
|