Back to Repositories

Testing Body Parse Error Handling in Grape API Framework

This test suite validates error handling and body parsing functionality in the Grape API framework, focusing on different content types and rescue handlers. It ensures proper handling of malformed request bodies and validation errors across JSON, XML, and plain text formats.

Test Coverage Overview

The test suite comprehensively covers body parsing error scenarios across multiple content types and rescue handler configurations.

  • Tests various content types: JSON, XML, text/plain
  • Validates different rescue handler implementations
  • Verifies error message formatting and status codes
  • Tests behavior with and without specific content types

Implementation Analysis

The testing approach uses RSpec to systematically verify Grape’s error handling mechanisms. It employs context-based organization to test different API configurations with rescue handlers and content types.

  • Uses subject-based API class instantiation
  • Implements before blocks for API setup
  • Validates response status and body content
  • Tests custom error message formatting

Technical Details

  • Testing Framework: RSpec
  • API Framework: Grape
  • Test Types: Unit tests
  • HTTP Methods: POST
  • Content Types: application/json, application/xml, text/plain
  • Status Codes: 400 Bad Request

Best Practices Demonstrated

The test suite exemplifies excellent testing practices through organized and thorough validation of error handling scenarios.

  • Consistent context organization
  • Comprehensive content type coverage
  • Clear test case isolation
  • Explicit expectation checking
  • DRY principle application through shared configurations

ruby-grape/grape

spec/grape/exceptions/body_parse_errors_spec.rb

            
# frozen_string_literal: true

describe Grape::Exceptions::ValidationErrors do
  context 'api with rescue_from :all handler' do
    subject { Class.new(Grape::API) }

    before do
      subject.rescue_from :all do |_e|
        error! 'message was processed', 400
      end
      subject.params do
        requires :beer
      end
      subject.post '/beer' do
        'beer received'
      end
    end

    def app
      subject
    end

    context 'with content_type json' do
      it 'can recover from failed body parsing' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
        expect(last_response.status).to eq 400
        expect(last_response.body).to eq('message was processed')
      end
    end

    context 'with content_type xml' do
      it 'can recover from failed body parsing' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
        expect(last_response.status).to eq 400
        expect(last_response.body).to eq('message was processed')
      end
    end

    context 'with content_type text' do
      it 'can recover from failed body parsing' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'text/plain'
        expect(last_response.status).to eq 400
        expect(last_response.body).to eq('message was processed')
      end
    end

    context 'with no specific content_type' do
      it 'can recover from failed body parsing' do
        post '/beer', 'test', {}
        expect(last_response.status).to eq 400
        expect(last_response.body).to eq('message was processed')
      end
    end
  end

  context 'api with rescue_from :grape_exceptions handler' do
    subject { Class.new(Grape::API) }

    before do
      subject.rescue_from :all do |_e|
        error! 'message was processed', 400
      end
      subject.rescue_from :grape_exceptions

      subject.params do
        requires :beer
      end
      subject.post '/beer' do
        'beer received'
      end
    end

    def app
      subject
    end

    context 'with content_type json' do
      it 'returns body parsing error message' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
        expect(last_response.status).to eq 400
        expect(last_response.body).to include 'message body does not match declared format'
      end
    end

    context 'with content_type xml' do
      it 'returns body parsing error message' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
        expect(last_response.status).to eq 400
        expect(last_response.body).to include 'message body does not match declared format'
      end
    end
  end

  context 'api with rescue_from :grape_exceptions handler with block' do
    subject { Class.new(Grape::API) }

    before do
      subject.rescue_from :grape_exceptions do |e|
        error! "Custom Error Contents, Original Message: #{e.message}", 400
      end

      subject.params do
        requires :beer
      end

      subject.post '/beer' do
        'beer received'
      end
    end

    def app
      subject
    end

    context 'with content_type json' do
      it 'returns body parsing error message' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
        expect(last_response.status).to eq 400
        expect(last_response.body).to include 'message body does not match declared format'
        expect(last_response.body).to include 'Custom Error Contents, Original Message'
      end
    end

    context 'with content_type xml' do
      it 'returns body parsing error message' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
        expect(last_response.status).to eq 400
        expect(last_response.body).to include 'message body does not match declared format'
        expect(last_response.body).to include 'Custom Error Contents, Original Message'
      end
    end
  end

  context 'api without a rescue handler' do
    subject { Class.new(Grape::API) }

    before do
      subject.params do
        requires :beer
      end
      subject.post '/beer' do
        'beer received'
      end
    end

    def app
      subject
    end

    context 'and with content_type json' do
      it 'can recover from failed body parsing' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
        expect(last_response.status).to eq 400
        expect(last_response.body).to include('message body does not match declared format')
        expect(last_response.body).to include('application/json')
      end
    end

    context 'with content_type xml' do
      it 'can recover from failed body parsing' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
        expect(last_response.status).to eq 400
        expect(last_response.body).to include('message body does not match declared format')
        expect(last_response.body).to include('application/xml')
      end
    end

    context 'with content_type text' do
      it 'can recover from failed body parsing' do
        post '/beer', 'test', 'CONTENT_TYPE' => 'text/plain'
        expect(last_response.status).to eq 400
        expect(last_response.body).to eq('beer is missing')
      end
    end

    context 'and with no specific content_type' do
      it 'can recover from failed body parsing' do
        post '/beer', 'test', {}
        expect(last_response.status).to eq 400
        # plain response with text/html
        expect(last_response.body).to eq('beer is missing')
      end
    end
  end
end