Last active
February 22, 2016 11:07
-
-
Save denyago/4d0e3a804f976f15d217 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'diff_matcher' | |
RSpec::Matchers.define :be_matching do |expected| | |
match do |actual| | |
@difference = DiffMatcher::Difference.new(normalize(expected), normalize(actual), opts) | |
no_errors_happened? && @difference.matching? | |
end | |
failure_message do |actual| | |
no_errors_happened? ? @difference.to_s : "Critical errors during matching:\n#{errors.join("\n")}" | |
end | |
chain :unordered do | |
@unordered = true | |
end | |
private | |
def opts | |
{color_enabled: RSpec::configuration.color_enabled?} | |
end | |
def normalize(obj) | |
result = obj | |
if result.kind_of?(String) | |
begin | |
result = MultiJson.load(obj) | |
rescue MultiJson::ParseError => e | |
errors << "Failed to parse JSON: #{e}\n String: #{obj.inspect}" | |
return {} | |
end | |
end | |
result = safe_deep_symbolize(result) | |
if result.kind_of?(Array) | |
result = result.map do |member| | |
safe_deep_symbolize(member) | |
end | |
end | |
if @unordered && result.kind_of?(Array) | |
result = result.sort_by do |member| | |
member[:id] || | |
member[member.keys.sort.map(&:to_s).grep(/id/).first.to_sym] | |
end | |
end | |
result | |
end | |
def safe_deep_symbolize(obj) | |
return obj unless obj.respond_to?(:deep_symbolize_keys) | |
obj.deep_symbolize_keys | |
end | |
def no_errors_happened? | |
errors.size == 0 | |
end | |
def errors | |
@errors ||= [] | |
end | |
end | |
module ToMatch | |
# DiffMatcher will treat it as Proc | |
class MatcherWrapper < Proc | |
def initialize(matcher) | |
@matcher = matcher | |
@message = "not yet matched" | |
end | |
def inspect | |
message | |
end | |
def call(actual) | |
good_result = matcher.matches?(actual) | |
if good_result | |
@message = actual.to_s | |
else | |
@message = matcher.failure_message || matcher.description | |
end | |
good_result | |
end | |
private | |
attr_reader :matcher, :message | |
end | |
def to_match(matcher) | |
MatcherWrapper.new(matcher) {} | |
end | |
end | |
RSpec.configure { |c| c.include ToMatch } | |
# Class is literally Proc, but with #inspect | |
# returning it's source for visual purposes. | |
# | |
# Can be used as a smart matcher for +be_matching+ | |
# matcher. | |
# | |
# Example (use): | |
# | |
# expect(subject).to be_matching({ | |
# runtime: DescriptiveProc.new {|sec| 2 > sec && sec > 0}, | |
# }) | |
# | |
# Example (output): | |
# | |
# { | |
# { | |
# :runtime=>{ 0.056614, | |
# } | |
# } | |
# Where, { 1 match_proc | |
# # ./spec/models/changes_tracker_spec.rb:104:in `block (4 levels) in <top (required)>' | |
# | |
# { | |
# { | |
# :runtime=>- proc { |sec| (1 > sec) and (sec > 0) }+ 10.0, | |
# } | |
# } | |
# Where, - 1 missing, + 1 additional | |
# # ./spec/models/changes_tracker_spec.rb:104:in `block (4 levels) in <top (required)>' | |
class DescriptiveProc < Proc | |
def inspect | |
to_source | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment