mcpField
R
rspec-service-testing

rspec-service-testing

github.com/midudevTesting & quality7.0k57 installsUpdated 1w ago
Repository

About rspec-service-testing

A skill for writing RSpec tests for service classes, aimed at Ruby and Rails developers. It emphasizes public contracts, isolated unit tests, database-backed integration tests, and a write-run-verify-fix workflow.

Key features

  1. 01Contract-focused service specs
  2. 02Unit isolation with instance_double
  3. 03Database integration with create
  4. 04API response hash factories
  5. 05Test workflow and checklist

Use cases

  1. 01Testing service success and error paths
  2. 02Separating unit and database-backed tests
  3. 03Building hash fixtures for API responses

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/midudev/autoskills --skill rspec-service-testing

After installing, type/rspec-service-testingin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

PATTERNS.md
assets/
spec_examples.md
testing_checklist.md

Details

From the project README

RSpec Service Testing

Use this skill when writing tests for service classes under spec/services/.

Core principle: Test the public contract (.call, .find, .search), not internal implementation. Use instance_double for isolation, create for integration.

Workflow: Write → Run → Verify → Fix

1. WRITE:   Write the spec (happy path + error cases + edge cases)
2. RUN:     bundle exec rspec spec/services/your_service_spec.rb
3. VERIFY:  Confirm failures are for the right reason (not a typo or missing factory)
4. FIX:     Implement or fix until the spec passes
5. SUITE:   bundle exec rspec spec/services/ — verify no regressions

DO NOT implement the service before step 1 is written and failing for the right reason.

Quick Reference

AspectRule
File locationspec/services/module_name/service_spec.rb
Subjectsubject(:service_call) { described_class.call(params) }
Unit isolationinstance_double for collaborators
Integrationcreate for DB-backed tests
Multi-assertionaggregate_failures
State verificationchange matchers
Time-dependenttravel_to
API responsesFactoryBot hash factories (class: Hash)

Spec Template

# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ModuleName::MainService do
  describe '.call' do
    subject(:service_call) { described_class.call(params) }

    let(:shelter) { create(:shelter, :with_animals) }
    let(:params) do
      { shelter: { shelter_id: shelter.id }, items: %w[TAG001 TAG002] }
    end

    context 'when input is valid' do
      before { create(:animal, tag_number: 'TAG001', shelter:) }

      it 'returns success' do
        expect(service_call[:success]).to be true
      end
    end

    context 'when shelter is not found' do
      let(:params) { super().merge(shelter: { shelter_id: 999_999 }) }

      it 'returns error response' do
        expect(service_call[:success]).to be false
      end
    end

    context 'when input is blank' do
      let(:params) { { shelter: { shelter_id: nil }, items: [] } }

      it 'returns error response with meaningful message' do
        aggregate_failures do
          expect(service_call[:success]).to be false
          expect(service_call[:errors]).not_to be_empty
        end
      end
    end
  end
end

Use instance_double for unit isolation:

let(:client) { instance_double(Api::Client) }
before { allow(client).to receive(:execute_query).and_return(api_response) }

Use create for integration tests:

let(:source_shelter) { create(:shelter, :with_animals) }

FactoryBot Hash Factories for API Responses

When testing API clients, use class: Hash with initialize_with to build hash-shaped response fixtures. A minimal example:

FactoryBot.define do
  factory :api_animal_response, class: Hash do
    tag_number { 'TAG001' }
    status     { 'active' }

    initialize_with { attributes.stringify_keys }
  end
end

# In the spec:
let(:api_response) { build(:api_animal_response, tag_number: 'TAG002') }

See PATTERNS.md for the full pattern and factory placement guidance.

New Test File Checklist

  • subject defined for the main action
  • instance_double for unit / create for integration
  • Happy path for each public method
  • Error and edge cases (blank input, invalid refs, failures)
  • Partial success scenarios where relevant
  • shared_examples for repeated patterns
  • aggregate_failures for multi-assertion tests
  • change matchers for state verification
  • Logger expectations for error logging

Common Mistakes

MistakeCorrect approach
No error scenario testsHappy path only = false confidence — always test failures
let! everywhereUse let (lazy) unless the value is needed unconditionally for setup
Huge factory setupKeep factories minimal — only attributes required for the test
Spec breaks when implementation changes but behavior is unchangedTests that break on refactoring are testing internals, not contracts

Integration

SkillWhen to chain
rspec-best-practicesFor general RSpec style and TDD discipline
ruby-service-objectsFor the service conventions being tested
ruby-api-client-integrationFor API client layer testing patterns
rails-engine-testingWhen testing engine-specific services

Assets

Comments

Markdown supported