require 'rails_helper'
RSpec.describe User, type: :model do
subject(:user) { build(:user) }
describe 'validations' do
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email).case_insensitive }
it { should validate_presence_of(:name) }
context 'when email format is invalid' do
before { user.email = 'invalid-email' }
it 'is invalid' do
expect(user).not_to be_valid
expect(user.errors[:email]).to include('is invalid')
end
end
end
describe 'associations' do
it { should have_many(:posts).dependent(:destroy) }
it { should have_one(:profile).dependent(:destroy) }
it { should have_many(:comments) }
end
describe '#full_name' do
let(:user) { build(:user, first_name: 'John', last_name: 'Doe') }
it 'returns the concatenated first and last name' do
expect(user.full_name).to eq('John Doe')
end
context 'when last name is missing' do
let(:user) { build(:user, first_name: 'John', last_name: nil) }
it 'returns only the first name' do
expect(user.full_name).to eq('John')
end
end
end
describe '#activate!' do
it 'changes status to active' do
expect { user.activate! }
.to change(user, :status).from('pending').to('active')
end
it 'sets activated_at timestamp' do
freeze_time do
user.activate!
expect(user.activated_at).to eq(Time.current)
end
end
it 'sends activation email' do
expect(UserMailer).to receive(:activation_email)
.with(user).and_call_original
expect { user.activate! }
.to have_enqueued_mail(UserMailer, :activation_email)
end
end
describe 'scopes' do
let!(:active_user) { create(:user, status: 'active') }
let!(:inactive_user) { create(:user, status: 'inactive') }
describe '.active' do
it 'returns only active users' do
expect(User.active).to contain_exactly(active_user)
end
end
end
end
# spec/support/shared_examples/timestampable.rb
RSpec.shared_examples 'timestampable' do
it { should respond_to(:created_at) }
it { should respond_to(:updated_at) }
it 'sets created_at on creation' do
freeze_time do
subject.save!
expect(subject.created_at).to eq(Time.current)
end
end
it 'updates updated_at on save' do
subject.save!
freeze_time do
subject.update!(name: 'Updated')
expect(subject.updated_at).to eq(Time.current)
end
end
end
# spec/support/shared_examples/soft_deletable.rb
RSpec.shared_examples 'soft deletable' do
describe '#soft_delete!' do
it 'sets deleted_at timestamp' do
freeze_time do
subject.soft_delete!
expect(subject.deleted_at).to eq(Time.current)
end
end
it 'does not remove record from database' do
expect { subject.soft_delete! }
.not_to change(described_class, :count)
end
end
describe '.active' do
let!(:deleted_record) { create(described_class.name.underscore.to_sym) }
before { deleted_record.soft_delete! }
it 'excludes soft deleted records' do
expect(described_class.active).not_to include(deleted_record)
end
end
end
# Usage in specs:
# RSpec.describe Post do
# it_behaves_like 'timestampable'
# it_behaves_like 'soft deletable'
# end
require 'rails_helper'
RSpec.describe UserRegistrationService do
subject(:service) { described_class.new(user_params, notifier: notifier) }
let(:user_params) { attributes_for(:user) }
let(:notifier) { instance_double(UserNotifier) }
describe '#call' do
before do
allow(notifier).to receive(:send_welcome_email)
allow(Analytics).to receive(:track)
end
context 'with valid parameters' do
it 'creates a new user' do
expect { service.call }
.to change(User, :count).by(1)
end
it 'creates a user profile' do
result = service.call
expect(result.user.profile).to be_present
end
it 'sends welcome email' do
service.call
expect(notifier).to have_received(:send_welcome_email)
end
it 'tracks registration event' do
service.call
expect(Analytics).to have_received(:track)
.with(hash_including(event: 'user_registered'))
end
it 'returns successful result' do
result = service.call
expect(result).to be_success
expect(result.user).to be_a(User)
end
end
context 'with invalid parameters' do
let(:user_params) { { email: 'invalid' } }
it 'does not create a user' do
expect { service.call }
.not_to change(User, :count)
end
it 'returns failed result with errors' do
result = service.call
expect(result).to be_failure
expect(result.errors).not_to be_empty
end
end
end
end
RSpec provides powerful testing tools for behavior-driven development. Shared examples reduce duplication across similar specs—I extract common behavior into reusable examples. Contexts organize tests by different scenarios. Let blocks lazily evaluate test data, improving performance. Subject defines the object under test. Before hooks set up test state; after hooks clean up. I use described_class for class references, enabling easy refactoring. Mocking and stubbing with double, allow, and expect isolate unit tests. Custom matchers improve test readability. Aggregate failures show all failures, not just the first. RSpec's expressiveness makes tests serve as living documentation. Well-structured specs ensure confidence in refactoring and feature additions.