243 lines
4.6 KiB
Markdown
243 lines
4.6 KiB
Markdown
---
|
||
name: rails-test-patterns
|
||
description: Ensures comprehensive test coverage following Rails testing best practices
|
||
auto_invoke: true
|
||
trigger_on: [file_create, file_modify]
|
||
file_patterns: ["**/spec/**/*_spec.rb", "**/test/**/*_test.rb"]
|
||
tags: [rails, testing, rspec, minitest, coverage, tdd]
|
||
priority: 2
|
||
version: 2.0
|
||
---
|
||
|
||
# Rails Test Patterns Skill
|
||
|
||
Automatically ensures test quality and comprehensive coverage.
|
||
|
||
## What This Skill Does
|
||
|
||
**Automatic Checks:**
|
||
- Test framework detection (RSpec vs Minitest)
|
||
- AAA pattern (Arrange-Act-Assert)
|
||
- Coverage thresholds (80%+ models, 70%+ controllers)
|
||
- Factory usage over fixtures
|
||
- No flaky tests (sleep, Time.now without freezing)
|
||
|
||
**When It Activates:**
|
||
- Test files created or modified
|
||
- New models/controllers added (ensures tests exist)
|
||
|
||
## Test Quality Checks
|
||
|
||
### 1. AAA Pattern
|
||
|
||
**Good:**
|
||
```ruby
|
||
RSpec.describe User do
|
||
describe '#full_name' do
|
||
it 'combines first and last name' do
|
||
# Arrange
|
||
user = User.new(first_name: 'John', last_name: 'Doe')
|
||
|
||
# Act
|
||
result = user.full_name
|
||
|
||
# Assert
|
||
expect(result).to eq('John Doe')
|
||
end
|
||
end
|
||
end
|
||
```
|
||
|
||
**Skill Output (if violated):**
|
||
```
|
||
⚠️ Test Pattern: AAA structure not clear
|
||
Location: spec/models/user_spec.rb:15
|
||
Recommendation: Separate Arrange, Act, Assert with comments
|
||
```
|
||
|
||
### 2. Framework Detection
|
||
|
||
**Detects:**
|
||
```ruby
|
||
# RSpec
|
||
describe Model do
|
||
it 'does something' do
|
||
expect(value).to eq(expected)
|
||
end
|
||
end
|
||
|
||
# Minitest
|
||
class ModelTest < ActiveSupport::TestCase
|
||
test "does something" do
|
||
assert_equal expected, value
|
||
end
|
||
end
|
||
```
|
||
|
||
### 3. Factory Usage
|
||
|
||
**Preferred:**
|
||
```ruby
|
||
# spec/factories/users.rb
|
||
FactoryBot.define do
|
||
factory :user do
|
||
email { Faker::Internet.email }
|
||
name { Faker::Name.name }
|
||
end
|
||
end
|
||
|
||
# In tests
|
||
user = create(:user)
|
||
```
|
||
|
||
**Skill Output:**
|
||
```
|
||
ℹ️ Test Pattern: Consider using FactoryBot
|
||
Location: spec/models/post_spec.rb:8
|
||
Current: User.create(name: 'Test', email: 'test@example.com')
|
||
Recommendation: Define factory and use create(:user)
|
||
```
|
||
|
||
### 4. Coverage Thresholds
|
||
|
||
**Checks:**
|
||
- Models: 80%+ coverage
|
||
- Controllers: 70%+ coverage
|
||
- Services: 90%+ coverage
|
||
|
||
**Skill Output:**
|
||
```
|
||
⚠️ Test Coverage: Below threshold
|
||
File: app/models/order.rb
|
||
Coverage: 65% (threshold: 80%)
|
||
Missing: #calculate_total, #apply_discount methods
|
||
|
||
Add tests for uncovered methods.
|
||
```
|
||
|
||
### 5. Flaky Test Detection
|
||
|
||
**Problem:**
|
||
```ruby
|
||
# BAD
|
||
it 'expires after 1 hour' do
|
||
user = create(:user)
|
||
sleep(3601) # ❌ Actual waiting
|
||
expect(user.expired?).to be true
|
||
end
|
||
|
||
# GOOD
|
||
it 'expires after 1 hour' do
|
||
user = create(:user)
|
||
travel 1.hour do # ✅ Time travel
|
||
expect(user.expired?).to be true
|
||
end
|
||
end
|
||
```
|
||
|
||
**Skill Output:**
|
||
```
|
||
❌ Test Anti-pattern: Using sleep in test
|
||
Location: spec/models/session_spec.rb:42
|
||
Issue: sleep() makes tests slow and flaky
|
||
|
||
Fix: Use time helpers
|
||
travel 1.hour do
|
||
# assertions here
|
||
end
|
||
```
|
||
|
||
## Test Types
|
||
|
||
### Model Tests
|
||
|
||
**Should test:**
|
||
- Validations
|
||
- Associations
|
||
- Scopes
|
||
- Instance methods
|
||
- Class methods
|
||
|
||
**Example:**
|
||
```ruby
|
||
RSpec.describe Post, type: :model do
|
||
describe 'validations' do
|
||
it { should validate_presence_of(:title) }
|
||
it { should validate_uniqueness_of(:slug) }
|
||
end
|
||
|
||
describe 'associations' do
|
||
it { should belong_to(:user) }
|
||
it { should have_many(:comments) }
|
||
end
|
||
|
||
describe '#published?' do
|
||
it 'returns true when published_at is set' do
|
||
post = build(:post, published_at: 1.day.ago)
|
||
expect(post.published?).to be true
|
||
end
|
||
end
|
||
end
|
||
```
|
||
|
||
### Request Tests
|
||
|
||
**Should test:**
|
||
- HTTP status codes
|
||
- Response body content
|
||
- Authentication requirements
|
||
- Authorization checks
|
||
|
||
**Example:**
|
||
```ruby
|
||
RSpec.describe 'Posts API', type: :request do
|
||
describe 'GET /api/v1/posts' do
|
||
it 'returns all posts' do
|
||
create_list(:post, 3)
|
||
|
||
get '/api/v1/posts'
|
||
|
||
expect(response).to have_http_status(:ok)
|
||
expect(JSON.parse(response.body).size).to eq(3)
|
||
end
|
||
|
||
context 'when not authenticated' do
|
||
it 'returns unauthorized' do
|
||
get '/api/v1/posts'
|
||
expect(response).to have_http_status(:unauthorized)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
```
|
||
|
||
## Configuration
|
||
|
||
```yaml
|
||
# .rails-testing.yml
|
||
coverage:
|
||
models: 80
|
||
controllers: 70
|
||
services: 90
|
||
|
||
patterns:
|
||
enforce_aaa: warning
|
||
require_factories: info
|
||
detect_flaky: error
|
||
|
||
framework:
|
||
auto_detect: true
|
||
prefer: rspec # or minitest
|
||
```
|
||
|
||
## References
|
||
|
||
- **RSpec Best Practices**: https://rspec.info/
|
||
- **Minitest Guide**: https://github.com/minitest/minitest
|
||
- **FactoryBot**: https://github.com/thoughtbot/factory_bot
|
||
- **Pattern Library**: /patterns/testing-patterns.md
|
||
|
||
---
|
||
|
||
**This skill ensures your Rails app has comprehensive, maintainable test coverage.**
|