Validating Multi-Format Response Parsing in HTTParty
This test suite comprehensively validates the HTTParty::Parser class, which handles response parsing for different data formats in the HTTParty library. It ensures robust parsing functionality for JSON, XML, CSV, and plain text formats while handling various edge cases and encoding scenarios.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
jnunemaker/httparty
spec/httparty/parser_spec.rb
require 'spec_helper'
require 'multi_xml'
RSpec.describe HTTParty::Parser do
describe ".SupportedFormats" do
it "returns a hash" do
expect(HTTParty::Parser::SupportedFormats).to be_instance_of(Hash)
end
end
describe ".call" do
it "generates an HTTParty::Parser instance with the given body and format" do
expect(HTTParty::Parser).to receive(:new).with('body', :plain).and_return(double(parse: nil))
HTTParty::Parser.call('body', :plain)
end
it "calls #parse on the parser" do
parser = double('Parser')
expect(parser).to receive(:parse)
allow(HTTParty::Parser).to receive_messages(new: parser)
parser = HTTParty::Parser.call('body', :plain)
end
end
describe ".formats" do
it "returns the SupportedFormats constant" do
expect(HTTParty::Parser.formats).to eq(HTTParty::Parser::SupportedFormats)
end
it "returns the SupportedFormats constant for subclasses" do
klass = Class.new(HTTParty::Parser)
klass::SupportedFormats = { "application/atom+xml" => :atom }
expect(klass.formats).to eq({"application/atom+xml" => :atom})
end
end
describe ".format_from_mimetype" do
it "returns a symbol representing the format mimetype" do
expect(HTTParty::Parser.format_from_mimetype("text/plain")).to eq(:plain)
end
it "returns nil when the mimetype is not supported" do
expect(HTTParty::Parser.format_from_mimetype("application/atom+xml")).to be_nil
end
end
describe ".supported_formats" do
it "returns a unique set of supported formats represented by symbols" do
expect(HTTParty::Parser.supported_formats).to eq(HTTParty::Parser::SupportedFormats.values.uniq)
end
end
describe ".supports_format?" do
it "returns true for a supported format" do
allow(HTTParty::Parser).to receive_messages(supported_formats: [:json])
expect(HTTParty::Parser.supports_format?(:json)).to be_truthy
end
it "returns false for an unsupported format" do
allow(HTTParty::Parser).to receive_messages(supported_formats: [])
expect(HTTParty::Parser.supports_format?(:json)).to be_falsey
end
end
describe "#parse" do
it "attempts to parse supported formats" do
parser = HTTParty::Parser.new('body', :json)
allow(parser).to receive_messages(supports_format?: true)
expect(parser).to receive(:parse_supported_format)
parser.parse
end
it "returns the unparsed body when the format is unsupported" do
parser = HTTParty::Parser.new('body', :json)
allow(parser).to receive_messages(supports_format?: false)
expect(parser.parse).to eq(parser.body)
end
it "returns nil for an empty body" do
parser = HTTParty::Parser.new('', :json)
expect(parser.parse).to be_nil
end
it "returns nil for a nil body" do
parser = HTTParty::Parser.new(nil, :json)
expect(parser.parse).to be_nil
end
it "returns nil for a 'null' body" do
parser = HTTParty::Parser.new("null", :json)
expect(parser.parse).to be_nil
end
it "returns nil for a body with spaces only" do
parser = HTTParty::Parser.new(" ", :json)
expect(parser.parse).to be_nil
end
it "does not raise exceptions for bodies with invalid encodings" do
parser = HTTParty::Parser.new("\x80", :invalid_format)
expect(parser.parse).to_not be_nil
end
it "ignores utf-8 bom" do
parser = HTTParty::Parser.new("\xEF\xBB\xBF\{\"hi\":\"yo\"\}", :json)
expect(parser.parse).to eq({"hi"=>"yo"})
end
it "parses ascii 8bit encoding" do
parser = HTTParty::Parser.new(
"{\"currency\":\"\xE2\x82\xAC\"}".force_encoding('ASCII-8BIT'),
:json
)
expect(parser.parse).to eq({"currency" => "€"})
end
it "parses frozen strings" do
parser = HTTParty::Parser.new('{"a":1}'.freeze, :json)
expect(parser.parse).to eq("a" => 1)
end
end
describe "#supports_format?" do
it "utilizes the class method to determine if the format is supported" do
expect(HTTParty::Parser).to receive(:supports_format?).with(:json)
parser = HTTParty::Parser.new('body', :json)
parser.send(:supports_format?)
end
end
describe "#parse_supported_format" do
it "calls the parser for the given format" do
parser = HTTParty::Parser.new('body', :json)
expect(parser).to receive(:json)
parser.send(:parse_supported_format)
end
context "when a parsing method does not exist for the given format" do
it "raises an exception" do
parser = HTTParty::Parser.new('body', :atom)
expect do
parser.send(:parse_supported_format)
end.to raise_error(NotImplementedError, "HTTParty::Parser has not implemented a parsing method for the :atom format.")
end
it "raises a useful exception message for subclasses" do
atom_parser = Class.new(HTTParty::Parser) do
def self.name
'AtomParser'
end
end
parser = atom_parser.new 'body', :atom
expect do
parser.send(:parse_supported_format)
end.to raise_error(NotImplementedError, "AtomParser has not implemented a parsing method for the :atom format.")
end
end
end
context "parsers" do
subject do
HTTParty::Parser.new('body', nil)
end
it "parses xml with MultiXml" do
expect(MultiXml).to receive(:parse).with('body')
subject.send(:xml)
end
it "parses json with JSON" do
expect(JSON).to receive(:parse).with('body', :quirks_mode => true, :allow_nan => true)
subject.send(:json)
end
it "parses html by simply returning the body" do
expect(subject.send(:html)).to eq('body')
end
it "parses plain text by simply returning the body" do
expect(subject.send(:plain)).to eq('body')
end
it "parses csv with CSV" do
require 'csv'
expect(CSV).to receive(:parse).with('body')
subject.send(:csv)
end
end
end