Claude Code Plugins

Community-maintained marketplace

Feedback

rspec-request-testing

@nekorush14/dotfiles
2
0

Write RSpec request specs for testing API endpoints, HTTP responses, authentication, and request/response handling. Use when testing controllers, API endpoints, or HTTP interactions following TDD.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name rspec-request-testing
description Write RSpec request specs for testing API endpoints, HTTP responses, authentication, and request/response handling. Use when testing controllers, API endpoints, or HTTP interactions following TDD.

RSpec Request Testing Specialist

Specialized in writing request specs for API endpoints and controller actions.

When to Use This Skill

  • Testing API endpoints (GET, POST, PUT, DELETE)
  • Testing HTTP status codes and response formats
  • Testing authentication and authorization
  • Testing request parameters and headers
  • Testing JSON/XML response structure
  • Creating request specs before implementation (TDD)

Core Principles

  • Test First: Write request specs before implementing endpoints
  • Full Stack: Test complete request/response cycle
  • Authentication: Test both authenticated and unauthenticated scenarios
  • Error Handling: Test error responses and edge cases
  • Response Format: Verify JSON structure and content

Request Spec Structure

# spec/requests/api/v1/users_spec.rb
require 'rails_helper'

RSpec.describe 'API::V1::Users', type: :request do
    describe 'GET /api/v1/users' do
        context 'when authenticated' do
            # Authenticated tests
        end

        context 'when not authenticated' do
            # Unauthenticated tests
        end
    end

    describe 'POST /api/v1/users' do
        context 'with valid parameters' do
            # Valid parameter tests
        end

        context 'with invalid parameters' do
            # Invalid parameter tests
        end
    end
end

Testing GET Requests

describe 'GET /api/v1/users' do
    let(:user) { create(:user) }
    let(:headers) { { 'Authorization' => "Bearer #{user.token}" } }

    before do
        create_list(:user, 3)
    end

    it 'returns list of users' do
        get '/api/v1/users', headers: headers

        expect(response).to have_http_status(:ok)
        expect(JSON.parse(response.body)['users'].size).to eq(4)
    end

    it 'returns users with correct attributes' do
        get '/api/v1/users', headers: headers

        user_data = JSON.parse(response.body)['users'].first
        expect(user_data).to include('id', 'email', 'name')
        expect(user_data).not_to include('password_digest')
    end

    it 'supports pagination' do
        get '/api/v1/users', params: { page: 1, per_page: 2 }, headers: headers

        expect(JSON.parse(response.body)['users'].size).to eq(2)
    end
end

Testing POST Requests

describe 'POST /api/v1/users' do
    let(:valid_params) do
        {
            user: {
                email: 'test@example.com',
                password: 'password123',
                name: 'Test User'
            }
        }
    end

    context 'with valid parameters' do
        it 'creates a new user' do
            expect {
                post '/api/v1/users', params: valid_params
            }.to change(User, :count).by(1)
        end

        it 'returns created status' do
            post '/api/v1/users', params: valid_params

            expect(response).to have_http_status(:created)
        end

        it 'returns user data' do
            post '/api/v1/users', params: valid_params

            user_data = JSON.parse(response.body)['user']
            expect(user_data['email']).to eq('test@example.com')
        end
    end

    context 'with invalid parameters' do
        let(:invalid_params) do
            { user: { email: 'invalid', password: '123' } }
        end

        it 'does not create a user' do
            expect {
                post '/api/v1/users', params: invalid_params
            }.not_to change(User, :count)
        end

        it 'returns unprocessable entity status' do
            post '/api/v1/users', params: invalid_params

            expect(response).to have_http_status(:unprocessable_entity)
        end

        it 'returns error messages' do
            post '/api/v1/users', params: invalid_params

            errors = JSON.parse(response.body)['errors']
            expect(errors).to be_present
        end
    end
end

Testing PUT/PATCH Requests

describe 'PUT /api/v1/users/:id' do
    let(:user) { create(:user) }
    let(:headers) { { 'Authorization' => "Bearer #{user.token}" } }

    context 'with valid parameters' do
        it 'updates the user' do
            put "/api/v1/users/#{user.id}",
                params: { user: { name: 'New Name' } },
                headers: headers

            expect(response).to have_http_status(:ok)
            expect(user.reload.name).to eq('New Name')
        end
    end

    context 'when unauthorized' do
        let(:other_user) { create(:user) }

        it 'returns forbidden status' do
            put "/api/v1/users/#{other_user.id}",
                params: { user: { name: 'New Name' } },
                headers: headers

            expect(response).to have_http_status(:forbidden)
        end
    end
end

Testing DELETE Requests

describe 'DELETE /api/v1/users/:id' do
    let(:user) { create(:user) }
    let(:headers) { { 'Authorization' => "Bearer #{user.token}" } }

    it 'deletes the user' do
        delete "/api/v1/users/#{user.id}", headers: headers

        expect(response).to have_http_status(:no_content)
        expect(User.find_by(id: user.id)).to be_nil
    end

    context 'when user does not exist' do
        it 'returns not found status' do
            delete '/api/v1/users/99999', headers: headers

            expect(response).to have_http_status(:not_found)
        end
    end
end

Testing Authentication

shared_examples 'requires authentication' do
    it 'returns unauthorized without token' do
        make_request

        expect(response).to have_http_status(:unauthorized)
    end

    it 'returns unauthorized with invalid token' do
        make_request(headers: { 'Authorization' => 'Bearer invalid' })

        expect(response).to have_http_status(:unauthorized)
    end
end

describe 'GET /api/v1/protected_resource' do
    def make_request(headers: {})
        get '/api/v1/protected_resource', headers: headers
    end

    it_behaves_like 'requires authentication'

    context 'when authenticated' do
        let(:user) { create(:user) }
        let(:headers) { { 'Authorization' => "Bearer #{user.token}" } }

        it 'returns protected data' do
            get '/api/v1/protected_resource', headers: headers

            expect(response).to have_http_status(:ok)
        end
    end
end

Testing JSON Responses

describe 'response format' do
    it 'returns JSON content type' do
        get '/api/v1/users', headers: headers

        expect(response.content_type).to include('application/json')
    end

    it 'has correct JSON structure' do
        get '/api/v1/users', headers: headers

        json = JSON.parse(response.body)
        expect(json).to have_key('users')
        expect(json).to have_key('meta')
    end

    it 'includes pagination metadata' do
        get '/api/v1/users', headers: headers

        meta = JSON.parse(response.body)['meta']
        expect(meta).to include('total_count', 'page', 'per_page')
    end
end

Testing Query Parameters

describe 'filtering' do
    before do
        create(:user, role: 'admin')
        create(:user, role: 'guest')
    end

    it 'filters by role' do
        get '/api/v1/users', params: { role: 'admin' }, headers: headers

        users = JSON.parse(response.body)['users']
        expect(users.all? { |u| u['role'] == 'admin' }).to be true
    end

    it 'searches by name' do
        john = create(:user, name: 'John Doe')
        jane = create(:user, name: 'Jane Smith')

        get '/api/v1/users', params: { q: 'John' }, headers: headers

        user_ids = JSON.parse(response.body)['users'].map { |u| u['id'] }
        expect(user_ids).to include(john.id)
        expect(user_ids).not_to include(jane.id)
    end
end

Tools to Use

  • Write: Create request spec files
  • Edit: Update request specs
  • Bash: Run request specs
  • Read: Read controller/API code

Bash Commands

# Run all request specs
bundle exec rspec spec/requests

# Run specific request spec
bundle exec rspec spec/requests/api/v1/users_spec.rb

Workflow

  1. Define API Contract: Understand endpoint requirements
  2. Write Failing Tests: Create specs for all scenarios
  3. Run Tests: Confirm tests fail
  4. Commit Tests: Commit test code
  5. Implementation: Implement controller/API
  6. Verify: Run tests and ensure they pass

Related Skills

  • rails-security: For authentication/authorization implementation
  • rails-error-handling: For error response implementation
  • rspec-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 all HTTP methods (GET, POST, PUT, DELETE)
  • Test authentication and authorization
  • Test both success and error responses
  • Verify HTTP status codes
  • Test JSON structure and content
  • Test query parameters and filtering
  • Test pagination
  • Keep tests independent
  • Mock external API calls