在RSpec中比较REXML元素的名称/属性相等性

时间:2015-05-06 21:56:43

标签: ruby rspec rspec3 rexml

是否存在用于在RSpec中比较REXML元素以实现逻辑相等的匹配器?我尝试编写一个自定义匹配器,将它们转换为格式化字符串,但如果属性顺序不同则失败。 (作为noted in the XML spec,属性的顺序不应该很重要。)

我可以通过编写一个比较名称,命名空间,子节点,属性等等的自定义匹配器来研磨,但这看起来很耗时且容易出错,如果其他人已经完成了它,我会d而不是重新发明轮子。

1 个答案:

答案 0 :(得分:0)

我最终使用equivalent-xml gem并编写了一个RSpec custom matcher来将REXML转换为Nokogiri,与equivalent-xml进行比较,并在需要时打印结果。

测试断言非常简单:

expect(actual).to be_xml(expected)

expect(actual).to be_xml(expected, path)

如果您想显示文件路径或某种标识符(例如,如果您要比较大量文档)。

匹配代码比它需要的更加迷人,因为它处理REXML,Nokogiri和字符串。

  module XMLMatchUtils
    def self.to_nokogiri(xml)
      return nil unless xml
      case xml
      when Nokogiri::XML::Element
        xml
      when Nokogiri::XML::Document
        xml.root
      when String
        to_nokogiri(Nokogiri::XML(xml, &:noblanks))
      when REXML::Element
        to_nokogiri(xml.to_s)
      else
        raise "be_xml() expected XML, got #{xml.class}"
      end
    end

    def self.to_pretty(nokogiri)
      return nil unless nokogiri
      out = StringIO.new
      save_options = Nokogiri::XML::Node::SaveOptions::FORMAT | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
      nokogiri.write_xml_to(out, encoding: 'UTF-8', indent: 2, save_with: save_options)
      out.string
    end

    def self.equivalent?(expected, actual, filename = nil)
      expected_xml = to_nokogiri(expected) || raise("expected value #{expected || 'nil'} does not appear to be XML#{" in #{filename}" if filename}")
      actual_xml = to_nokogiri(actual)

      EquivalentXml.equivalent?(expected_xml, actual_xml, element_order: false, normalize_whitespace: true)
    end

    def self.failure_message(expected, actual, filename = nil)
      expected_string = to_pretty(to_nokogiri(expected))
      actual_string = to_pretty(to_nokogiri(actual)) || actual

      # Uncomment this to dump expected/actual to file for manual diffing
      #
      # now = Time.now.to_i
      # FileUtils.mkdir('tmp') unless File.directory?('tmp')
      # File.open("tmp/#{now}-expected.xml", 'w') { |f| f.write(expected_string) }
      # File.open("tmp/#{now}-actual.xml", 'w') { |f| f.write(actual_string) }

      diff = Diffy::Diff.new(expected_string, actual_string).to_s(:text)

      "expected XML differs from actual#{" in #{filename}" if filename}:\n#{diff}"
    end

    def self.to_xml_string(actual)
      to_pretty(to_nokogiri(actual))
    end

    def self.failure_message_when_negated(actual, filename = nil)
      "expected not to get XML#{" in #{filename}" if filename}:\n\t#{to_xml_string(actual) || 'nil'}"
    end
  end

实际的匹配器非常简单:

  RSpec::Matchers.define :be_xml do |expected, filename = nil|
    match do |actual|
      XMLMatchUtils.equivalent?(expected, actual, filename)
    end

    failure_message do |actual|
      XMLMatchUtils.failure_message(expected, actual, filename)
    end

    failure_message_when_negated do |actual|
      XMLMatchUtils.failure_message_when_negated(actual, filename)
    end
  end