service-test
Skill2 starsChanged 35 days agoMITView on GitHub
Write or review the RSpec spec for one service object (app/services) so every business decision, side effect, external-API contract, and error path is guarded by an assertion that would actually fail, with no padding (constructor echoes, be_a(Result), one-assertion-per-example re-runs, private-method sends). Use when the user asks for service tests, points at a file in app/services or spec/services, or wants a service spec trimmed, sped up, or checked for whether it catches bugs.
In this repository75 other files it gives its agents.
- acceptance-criteriaSkill
- architect-guideSkill
- architect-reviewSkill
- audit-fixerSkill
- avo-admin-expertSkill
- backend-services-expertSkill
- bug-hunt-allSkill
- bug-hunt-fixSkill
- bug-huntSkill
- build-feature-remoteSkill
- build-featureSkill
- codeSkill
- component-testSkill
- controller-expertSkill
- create-qa-documentSkill
- debugSkill
- devops-expertSkill
- execute-qaSkill
- feature-qaSkill
- fix-bug-indexSkill
- fix-honeybadgerSkill
- fix-spec-bugsSkill
- fix-system-testSkill
- frontend-expertSkill
- frontend-reviewSkill
- full-qa-fixSkill
- full-qaSkill
- full-reviewSkill
- green-ciSkill
- group-issuesSkill
- honeybadger-auditSkill
- ideateSkill
- impact-assessmentSkill
- improveSkill
- merge-mainSkill
- model-expertSkill
- model-testSkill
- native-bug-hunt-fixSkill
- native-bug-huntSkill
- page-speedSkill
- plan-questionsSkill
- planSkill
- plan-system-testsSkill
- policy-testSkill
- prdSkill
- prd-worktreeSkill
- product-questionSkill
- product-question-tldrSkill
- qa-branchSkill
- qa-testerSkill
- refactor-sweepSkill
- resolve-honeybadgerSkill
- resolve-issueSkill
- resolve-issuesSkill
- review-acceptance-criteriaSkill
- review-fixesSkill
- review-railsSkill
- rspec-test-expertSkill
- scale-reviewSkill
- settle-pr-reviewSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 9
Write or review the spec for the service at $ARGUMENTS.
- Distinctive: line 15
If the spec exists you are in review mode.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Service Test
Write or review the spec for the service at $ARGUMENTS. First read
.claude/skills/rspec-test-expert/test-quality.md (the fat catalogue, the proof via coverage plus
probes, concurrency-safe spec runs, and the review workflow). This file covers what is particular
to services.
Map paths: app/services/billing/charge_service.rb ↔ spec/services/billing/charge_service_spec.rb.
If the spec exists you are in review mode; otherwise write mode.
Service shapes
- Callable (
include Callable):described_class.call(**kwargs)returns aResult, built bysuccess(**data)orfailure(message, **data). Assertbe_success/be_failure,result.data[:key]with exact values, andresult.errorwith the exact message when a user will see it.CallableandResulthave their own specs, so don't re-test.calldelegating tonew. - Domain-object services (
FooService.new(record).do_thing): test each public method the controllers or jobs call. Find the call sites (grep -rn "FooService" app) to know which methods are the real interface and what the callers do with the return value. - Class-method facades (
.validate_step): if a class method only doesnew(...).same_method, one example through it is enough. Test the logic once, not through both doors.
What to guard
- Each decision in
call. Guards and early returns (each produces its own failure), branches on state/role/payer/type, loops that skip items. One scenario per branch; for comparisons, the boundary value and one past it. - Side effects, with their content. Records created or updated (
reloadand assert the fields that matter), jobs enqueued with arguments, mail enqueued to the right recipient, broadcasts. Also the negative: on failure, nothing was written (not_to change) or the transaction rolled back. - External-API contract. Stub at the boundary (Stripe, Stedi, Vertex AI, RingRx, Google,
mediasoup, HTTP via WebMock/VCR) and assert the arguments sent (
hash_including(amount: 15_000, payment_intent: "pi_…"), idempotency key present). Then assert how each response shape (success, declined, rate-limited, timeout) maps to the Result. - The rescue ladder. The app's services rescue specific exceptions, then a catch-all, and call
Honeybadger.notify(e, context: {service: …, external_service: …})(CLAUDE.md). Each rescue branch gets an example asserting the user-facingfailuremessage and the notify with its context. Ops relies on both. This is the branch the suite most often leaves unexecuted. - PHI safety where it applies. Notify context, log payloads, and push payloads carry no PHI. Assert the absence when the service builds such a payload.
Service-specific fat
#initializeblocks: "sets the document",instance_variable_get, "accepts nil without error".be_a(Result),be_a(Hash),have_key(:errors),be_a(Integer),be(true).or be(false).- Re-run padding. Six examples that each call the service again to check one more field:
returns success/returns payment/returns refund_id/marks payment refunded/creates Stripe refund. Make it one example per scenario,:aggregate_failures, asserting all facets. It's faster too, because every example rebuilds the client, invoice, and payment graph. .send(:private_step)tests. Drive the branch throughcall. Keep asendtest only when deleting it would let a probe survive and the public path needs absurd setup; then note the design smell in findings.allow(service).to receive(:valid?)-style stubs of the subject's own methods, and stubs of cheap in-app models or services.Rails.loggerexpectations (keep the Honeybadger ones).- "Integration scenario" blocks that repeat the happy path already covered above.
- Constants re-listed (
ASSESSMENT_FIELD_OPTIONS.keys.size == 15,to be_frozen).
Shape
# frozen_string_literal: true
require "rails_helper"
RSpec.describe StripeRefundService, type: :service do
let(:payment) { create(:payment, :completed, amount_cents: 15_000, stripe_payment_intent_id: "pi_123") }
let(:user) { create(:user, :admin) }
def refund(**overrides) = described_class.call(payment:, reason: :requested_by_customer, refunded_by: user, **overrides)
context "when Stripe accepts the full refund" do
before { allow(Stripe::Refund).to receive(:create).and_return(double(id: "re_1", status: "succeeded")) }
it "refunds the whole amount and records it", :aggregate_failures do
result = refund
expect(Stripe::Refund).to have_received(:create)
.with(hash_including(payment_intent: "pi_123", amount: 15_000, reason: "requested_by_customer"), hash_including(:idempotency_key))
expect(result).to be_success
expect(result.data).to include(refund_id: "re_1", refund_amount: 15_000)
expect(payment.reload).to have_attributes(status: "refunded", refunded_by: user)
end
end
context "when Stripe raises" do
before { allow(Stripe::Refund).to receive(:create).and_raise(Stripe::InvalidRequestError.new("charge already refunded", nil)) }
it "fails with the user-facing message, reports, and leaves the payment untouched", :aggregate_failures do
allow(Honeybadger).to receive(:notify)
expect { refund }.not_to change { payment.reload.status }
expect(refund.error).to eq("…exact message from the service…")
expect(Honeybadger).to have_received(:notify).with(kind_of(Stripe::InvalidRequestError), hash_including(context: hash_including(external_service: "stripe")))
end
end
end(Illustrative: read the real service for names, statuses, and messages.)
Prove it, then report
Green, then coverage --baseline with no lost_lines. Then 2–5 probes aimed at the main branch
condition, a boundary, a value written or returned, an argument sent to the external API, and a
rescue branch's failure or notify line, all KILLED. Lint with bin/standardrb <spec>, and
report per test-quality.md or the caller's format.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

