Generates complete, runnable RSpec tests for Rails models, services, controllers, and background jobs following project conventions...
This skill generates comprehensive, project-specific RSpec tests that follow the Game Commissioner Rails application's testing conventions.
Automatically invoke when:
Key Triggers:
.rb files in app/models/, app/services/, app/controllers/, or app/jobs/.create!RSpec.describe ClassName, type: :model do
describe "#method_name" do
context "when specific condition" do
it "does something specific" do
# test implementation
end
end
end
end
Determine code type:
app/models/*.rb.method_name)initialize and instance methodsapp/controllers/*_controller.rbapp/jobs/*_job.rbExtract key information:
Model Tests (templates/model_spec.rb.erb):
address fieldService Tests - Class Methods (templates/service_class_method_spec.rb.erb):
EligibilityFilterService, CandidateFinder, AssignmentBuilderService Tests - Instance Methods (templates/service_instance_method_spec.rb.erb):
OrchestratorService, Ollama::ClientService, ResponseParserService#initialize and instance methodsdescribed_class.new patternController Tests (templates/controller_spec.rb.erb):
Job Tests (templates/job_spec.rb.erb):
#perform methodDO NOT generate stubs. Generate complete, runnable tests with:
let blocks for test data# Use real ActiveRecord objects
let(:model) { Model.create!(name: "Test", attribute: value) }
# Geocoder setup if model has addresses
before do
Geocoder.configure(lookup: :test)
Geocoder::Lookup::Test.add_stub("123 Test St", [{ "latitude" => 40.7, "longitude" => -74 }])
end
after do
Geocoder::Lookup::Test.reset
end
# Test validations
it "is valid with valid attributes" do
expect(model).to be_valid
end
it "is invalid without required_field" do
model.required_field = nil
expect(model).not_to be_valid
expect(model.errors[:required_field]).to include("can't be blank")
end
# Test associations
it "belongs to parent" do
expect(Model.reflect_on_association(:parent).macro).to eq(:belongs_to)
end
# Test enums
it "defines status enum" do
expect(Model.defined_enums["status"]).to eq({ "pending" => 0, "active" => 1 })
end
# Use doubles for dependencies
let(:dependency) { double("DependencyClass", id: 1, method: "value") }
let(:relation) { double("ActiveRecord::Relation") }
# Stub dependency behavior
before do
allow(DependencyClass).to receive(:method).and_return(relation)
allow(relation).to receive(:where).and_return([dependency])
end
# Test class methods
describe ".class_method" do
context "when input is valid" do
it "returns expected result" do
result = described_class.class_method(dependency)
expect(result).to eq(expected_value)
end
end
context "when input is invalid" do
it "handles gracefully" do
result = described_class.class_method(nil)
expect(result).to be_empty
end
end
end
# Initialize service with config
let(:config_param) { "default_value" }
let(:service) { described_class.new(config_param: config_param) }
# Test initialization
describe "#initialize" do
it "initializes with default config" do
expect { described_class.new }.not_to raise_error
end
it "initializes with custom config" do
custom = described_class.new(config_param: "custom")
expect(custom.instance_variable_get(:@config_param)).to eq("custom")
end
end
# Test instance methods
describe "#instance_method" do
let(:input) { "test input" }
context "when successful" do
before do
# Stub dependencies or HTTP requests
end
it "returns expected result" do
result = service.instance_method(input)
expect(result[:key]).to eq(expected)
end
end
context "when error occurs" do
before do
allow(dependency).to receive(:method).and_raise(StandardError)
end
it "raises appropriate error" do
expect { service.instance_method(input) }.to raise_error(StandardError)
end
end
end
# Use WebMock for HTTP requests
before do
stub_request(:post, "http://localhost:11434/api/generate")
.with(body: hash_including(model: "llama3.2"))
.to_return(
status: 200,
body: { response: "test response" }.to_json,
headers: { "Content-Type" => "application/json" }
)
end
# Test both success and failure
context "when API call succeeds" do
it "parses response correctly" do
result = service.call
expect(result[:response]).to eq("test response")
end
end
context "when API call fails" do
before do
stub_request(:post, url).to_return(status: 500)
end
it "raises an error" do
expect { service.call }.to raise_error(/API error/)
end
end
describe "GET #index" do
it "returns a successful response" do
get :index
expect(response).to be_successful
end
it "assigns @resources" do
resources = Resource.all
get :index
expect(assigns(:resources)).to eq(resources)
end
end
describe "POST #create" do
context "with valid parameters" do
let(:valid_attributes) { { name: "Test" } }
it "creates a new resource" do
expect {
post :create, params: { resource: valid_attributes }
}.to change(Resource, :count).by(1)
end
it "redirects to the created resource" do
post :create, params: { resource: valid_attributes }
expect(response).to redirect_to(Resource.last)
end
end
context "with invalid parameters" do
it "does not create a resource" do
expect {
post :create, params: { resource: { name: nil } }
}.not_to change(Resource, :count)
end
end
end
describe "#perform" do
let(:param) { double("Model", id: 1) }
let(:service) { instance_double(ServiceClass) }
before do
allow(ServiceClass).to receive(:new).and_return(service)
allow(service).to receive(:perform_action)
end
it "calls the service with correct parameters" do
expect(service).to receive(:perform_action).with(param)
described_class.perform_now(param)
end
context "when service raises an error" do
before do
allow(service).to receive(:perform_action).and_raise(StandardError)
end
it "handles the error" do
expect { described_class.perform_now(param) }.to raise_error(StandardError)
end
end
end
spec/models/model_name_spec.rbspec/services/module_name/service_name_spec.rb (mirror app/ structure)spec/controllers/controller_name_spec.rbspec/jobs/job_name_spec.rbExamples:
app/models/game.rb → spec/models/game_spec.rbapp/services/ai_assignment/orchestrator.rb → spec/services/ai_assignment/orchestrator_spec.rbapp/controllers/games_controller.rb → spec/controllers/games_controller_spec.rbapp/jobs/assign_open_games_job.rb → spec/jobs/assign_open_games_job_spec.rb# Doubles with type hints
let(:game) { double("Game", id: 1, name: "Test Game", status: "open") }
let(:official) { double("Official", id: 10, name: "John Doe") }
# ActiveRecord relation doubles
let(:relation) { double("ActiveRecord::Relation") }
allow(Game).to receive(:upcoming).and_return(relation)
allow(relation).to receive(:includes).and_return([game])
# Method stubbing
allow(object).to receive(:method).and_return(value)
allow(object).to receive(:method).with(args).and_return(value)
allow(object).to receive(:method).and_raise(StandardError)
# Value expectations
expect(result).to eq(expected)
expect(result).to be_truthy / be_falsy
expect(result).to be_nil
expect(result[:key]).to include(value)
expect(result).to match(/regex/)
# Behavior expectations
expect(service).to receive(:method).with(args)
expect { action }.to change(Model, :count).by(1)
expect { action }.to raise_error(ErrorClass)
expect { action }.not_to raise_error
# Model validation expectations
expect(model).to be_valid
expect(model).not_to be_valid
expect(model.errors[:field]).to include("error message")
# Basic stub
stub_request(:post, "http://example.com/api")
.to_return(status: 200, body: json, headers: {})
# With request matching
stub_request(:post, url)
.with(
body: hash_including(key: "value"),
headers: { "Content-Type" => "application/json" }
)
.to_return(status: 200, body: response.to_json)
# Error responses
stub_request(:post, url).to_return(status: 500)
stub_request(:post, url).to_raise(StandardError)
When generating tests, provide:
spec/[type]/[name]_spec.rbbundle exec rspec spec/path/to/file_spec.rbUser creates:
# app/models/league.rb
class League < ApplicationRecord
has_many :games
validates :name, presence: true, uniqueness: true
validates :season, presence: true
enum status: { active: 0, inactive: 1 }
scope :active, -> { where(status: :active) }
end
You generate:
# spec/models/league_spec.rb
require "rails_helper"
RSpec.describe League, type: :model do
describe "validations" do
let(:league) { League.new(name: "Test League", season: "2024", status: :active) }
it "is valid with valid attributes" do
expect(league).to be_valid
end
context "name" do
it "is invalid without a name" do
league.name = nil
expect(league).not_to be_valid
expect(league.errors[:name]).to include("can't be blank")
end
it "is invalid with duplicate name" do
League.create!(name: "Test League", season: "2024")
duplicate = League.new(name: "Test League", season: "2025")
expect(duplicate).not_to be_valid
expect(duplicate.errors[:name]).to include("has already been taken")
end
end
context "season" do
it "is invalid without a season" do
league.season = nil
expect(league).not_to be_valid
expect(league.errors[:season]).to include("can't be blank")
end
end
end
describe "associations" do
it "has many games" do
expect(League.reflect_on_association(:games).macro).to eq(:has_many)
end
end
describe "enums" do
it "defines status enum" do
expect(League.defined_enums["status"]).to eq({
"active" => 0,
"inactive" => 1
})
end
end
describe "scopes" do
describe ".active" do
it "returns only active leagues" do
active = League.create!(name: "Active League", season: "2024", status: :active)
inactive = League.create!(name: "Inactive League", season: "2024", status: :inactive)
expect(League.active).to include(active)
expect(League.active).not_to include(inactive)
end
end
end
end
Explanation: Created comprehensive model spec testing all validations (presence, uniqueness), associations, enums, and scopes. Uses real ActiveRecord objects per project conventions.
Next steps:
Run: bundle exec rspec spec/models/league_spec.rb
double("Game", attributes)For more detailed patterns, see:
references/testing_patterns.md - Comprehensive pattern guide../claude_project_rules.md - Project-wide conventionsspec/ - Real examples from this project