rspec-service-testing
Write RSpec tests for service objects testing business logic, transaction handling, and complex workflows. Use when testing service objects, form objects, or multi-step business processes following TDD.
$ Instalar
git clone https://github.com/nekorush14/dotfiles /tmp/dotfiles && cp -r /tmp/dotfiles/configs/claude/skills/rspec-service-testing ~/.claude/skills/dotfiles// tip: Run this command in your terminal to install the skill
SKILL.md
name: rspec-service-testing description: Write RSpec tests for service objects testing business logic, transaction handling, and complex workflows. Use when testing service objects, form objects, or multi-step business processes following TDD.
RSpec Service Testing Specialist
Specialized in writing comprehensive specs for service objects and business logic.
When to Use This Skill
- Testing service object business logic
- Testing multi-step workflows
- Testing transaction behavior and rollbacks
- Testing error handling in services
- Testing Result object patterns
- Creating service specs before implementation (TDD)
Core Principles
- Test First: Write service specs before implementing logic
- Comprehensive: Test success, failure, and edge cases
- Isolation: Mock external dependencies
- Transaction Testing: Verify rollback behavior
- Result Objects: Test structured return values
Service Spec Structure
# spec/services/order_processing_service_spec.rb
require 'rails_helper'
RSpec.describe OrderProcessingService do
describe '#call' do
let(:order) { create(:order) }
let(:service) { described_class.new(order) }
context 'with valid order' do
# Success scenarios
end
context 'with invalid order' do
# Failure scenarios
end
context 'when external service fails' do
# Error handling tests
end
end
end
Testing Service Success
describe '#call' do
let(:user) { create(:user) }
let(:order) { create(:order, user: user) }
let(:service) { described_class.new(order) }
context 'with valid order' do
before do
create_list(:order_item, 2, order: order)
end
it 'returns success result' do
result = service.call
expect(result).to be_success
expect(result.data).to eq(order)
end
it 'updates order status' do
expect {
service.call
}.to change { order.reload.status }.from('pending').to('confirmed')
end
it 'charges payment' do
expect(PaymentGateway).to receive(:charge).with(order)
service.call
end
it 'updates inventory' do
product = order.products.first
expect {
service.call
}.to change { product.reload.stock }.by(-1)
end
it 'sends confirmation email' do
expect {
service.call
}.to have_enqueued_job(OrderConfirmationJob).with(order.id)
end
it 'creates transaction log' do
expect {
service.call
}.to change(TransactionLog, :count).by(1)
end
end
end
Testing Service Failure
context 'with invalid order' do
let(:order) { create(:order, :empty) }
it 'returns failure result' do
result = service.call
expect(result).to be_failure
expect(result.error).to be_present
end
it 'does not change order status' do
expect {
service.call
}.not_to change { order.reload.status }
end
it 'does not charge payment' do
expect(PaymentGateway).not_to receive(:charge)
service.call
end
it 'returns descriptive error message' do
result = service.call
expect(result.error).to include('Order must have items')
end
end
Testing Error Handling
context 'when payment fails' do
before do
create_list(:order_item, 2, order: order)
allow(PaymentGateway).to receive(:charge).and_raise(PaymentError, 'Insufficient funds')
end
it 'returns failure result' do
result = service.call
expect(result).to be_failure
expect(result.error_type).to eq(:payment_failed)
end
it 'logs error' do
expect(Rails.logger).to receive(:error).with(/PaymentError/)
service.call
end
it 'does not update order status' do
expect {
service.call
}.not_to change { order.reload.status }
end
end
Testing Transaction Rollback
describe 'transaction rollback' do
before do
create_list(:order_item, 2, order: order)
allow(PaymentGateway).to receive(:charge).and_raise(PaymentError)
end
it 'rolls back all changes on error' do
initial_status = order.status
initial_stock = order.products.first.stock
service.call
expect(order.reload.status).to eq(initial_status)
expect(order.products.first.reload.stock).to eq(initial_stock)
end
it 'does not create transaction log on rollback' do
expect {
service.call
}.not_to change(TransactionLog, :count)
end
end
Testing Service with Options
describe 'service options' do
it 'uses custom payment method' do
service = described_class.new(order, payment_method: 'credit_card')
expect(PaymentGateway).to receive(:charge).with(order, method: 'credit_card')
service.call
end
it 'skips email when skip_email option is true' do
service = described_class.new(order, skip_email: true)
expect {
service.call
}.not_to have_enqueued_job(OrderConfirmationJob)
end
end
Testing Multiple Scenarios
describe 'payment processing' do
context 'when amount is under limit' do
let(:order) { create(:order, total: 100) }
it 'processes without additional verification' do
expect(PaymentGateway).not_to receive(:verify)
service.call
end
end
context 'when amount exceeds limit' do
let(:order) { create(:order, total: 10000) }
it 'requires additional verification' do
expect(PaymentGateway).to receive(:verify).with(order)
service.call
end
end
end
Testing Form Objects
# spec/forms/user_registration_form_spec.rb
RSpec.describe UserRegistrationForm do
describe '#save' do
let(:valid_params) do
{
email: 'test@example.com',
password: 'password123',
password_confirmation: 'password123',
terms_accepted: '1'
}
end
let(:form) { described_class.new(valid_params) }
context 'with valid parameters' do
it 'creates a user' do
expect {
form.save
}.to change(User, :count).by(1)
end
it 'creates user profile' do
expect {
form.save
}.to change(UserProfile, :count).by(1)
end
it 'sends welcome email' do
expect {
form.save
}.to have_enqueued_job(WelcomeEmailJob)
end
it 'returns created user' do
user = form.save
expect(user).to be_a(User)
expect(user).to be_persisted
end
end
context 'with invalid parameters' do
let(:invalid_form) { described_class.new(email: 'invalid') }
it 'does not create user' do
expect {
invalid_form.save
}.not_to change(User, :count)
end
it 'returns false' do
expect(invalid_form.save).to be false
end
it 'adds validation errors' do
invalid_form.save
expect(invalid_form.errors[:email]).to be_present
end
end
end
describe 'validations' do
it { should validate_presence_of(:email) }
it { should validate_presence_of(:password) }
it { should validate_acceptance_of(:terms_accepted) }
end
end
Tools to Use
Write: Create service spec filesEdit: Update service specsBash: Run service specsRead: Read service implementation
Bash Commands
# Run all service specs
bundle exec rspec spec/services
# Run specific service spec
bundle exec rspec spec/services/order_processing_service_spec.rb
Workflow
- Understand Business Logic: Clarify service requirements
- Write Failing Tests: Create specs for all scenarios
- Run Tests: Confirm tests fail
- Commit Tests: Commit test code
- Implementation: Use
rails-service-objectsskill - Verify: Run tests and ensure they pass
Related Skills
rails-service-objects: For service implementationrails-transactions: For transaction logicrails-error-handling: For error handlingrspec-model-testing: For testing underlying models
RSpec Fundamentals
See RSpec Testing Fundamentals
FactoryBot Guide
See FactoryBot Guide
TDD Workflow
Follow TDD Workflow
Key Reminders
- Test both success and failure paths
- Test transaction rollback behavior
- Mock external dependencies (payment, email, APIs)
- Test Result object structure
- Verify side effects (emails, logs, updates)
- Test edge cases and error conditions
- Keep tests independent
- Use descriptive context names
Repository

nekorush14
Author
nekorush14/dotfiles/configs/claude/skills/rspec-service-testing
2
Stars
0
Forks
Updated4d ago
Added1w ago