RSpec 2和3如何创建一个生成一堆示例的帮助器?

时间:2014-08-07 20:59:56

标签: ruby json rspec

我正在尝试创建帮助程序以统一和好的方式测试JSON响应。

对于更具描述性和特定性的失败,我想为每个JSON原子生成一个示例。

语法示例:

RSpec.describe "some JSON API View" do

  setup_objects

  before { render }

  describe "response" do

    subject { rendered }

    it_conforms_to_json(
      id: 27,
      email: "john.smith@example.com",
      name: "John",
      profile_description: %r{professional specialist},
      nested: {
        id: 37,
        status: "rejected"
      }
    )

  end

end

所以这段代码将等同于:

RSpec.describe "some JSON API View" do

  setup_objects

  before { render }

  describe "response" do

    subject { rendered }

    it 'has equal value at object["id"]' do
      expect(subject["id"]).to eq(27)
    end

    it 'has equal value at object["email"]' do
      expect(subject["email"]).to eq("john.smith@example.com")
    end

    it 'has equal value at object["name"]' do
      expect(subject["name"]).to eq("John")
    end

    it 'has matching value at object["profile_description"]' do
      expect(subject["profile_description"]).to match(%r{professional specialist})
    end

    it 'has equal value at object["nested"]["id"]' do
      expect(subject["nested"]["id"]).to eq(37)
    end

    it 'has equal value at object["nested"]["status"]' do
      expect(subject["nested"]["status"]).to eq("rejected")
    end

  end

end

我能够通过这个片段轻松实现这一目标:

module JsonHelper
  extend self

  def it_conforms_to_json(json)
    generate_examples_for(json)
  end

  private

  def generate_examples_for(json, opts)
    with_prefix = opts.fetch(:with_prefix, [])

    if json.is_a?(Hash)

      json.each do |key, new_json|
        new_prefix = with_prefix + [key.to_s]
        generate_examples_for(new_json, opts.merge(with_prefix: new_prefix))
      end

    elsif json.is_a?(Array)

      raise NotImplemented.new("Arrays are not allowed yet")

    elsif json.is_a?(String) || json.is_a?(Numeric)

      it "is expected to have equal value at json[\"#{with_prefix.join('"]["')}\"]" do
        value = JSON.parse(subject)
        with_prefix.each { |key| value = value[key.to_s] }
        expect(value).to eq(json)
      end

    end
  end
end

只需通过扩展它来启用它:rspec_config.extend JsonHelper

当您考虑相当失败的消息时,明显的问题就会出现:

  • 他们显示回溯,包括it "is expected...bla-bla-bla
  • 的位置
  • 他们排除it_conforms_to_json(...)来电
  • 的位置

首先可以通过添加回溯排除/清除模式来修复,但它会导致完全回溯,因为所有内容都已过滤。

可以通过在expect中包含begin..rescue语句来修复第二个和上一个,通过在file_path:line it_conforms_to_json(...) find_first_non_rspec_line调用之前添加回溯并重新引发已修改的异常来修复回溯。< / p>

在解决了前两个问题之后,出现了新问题:

  • “无法从回溯中找到匹配的行”

可疑方法是it(或类似的),通过应用默认的rspec排除正则表达式,在LIB_REGEX调用的回溯中找到第一行(非异常回溯)。

可以通过RSpec内部控制来修复,即:

  • 对于rspec 2:应用此正则表达式的猴子补丁方法
  • 对于rspec 3:取消定义常量IGNORE_REGEX并再次定义它将此辅助文件添加到此正则表达式
  • 对于rspec 3(最近的次要/补丁版本):相同,但 Failures: 1) A json response is expected to have equal value at json["id"] Failure/Error: it_conforms_to_json( expected: 37 got: 25 (compared using ==) # ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>' 2) A json response is expected to have equal value at json["name"] Failure/Error: it_conforms_to_json( expected: "Smith" got: "John" (compared using ==) # ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'

这个修补程序的代码变得糟糕且难以理解,并且它将继续维护,因为它取决于rspec内部实现,可以从次要/补丁版本更改为版本。谁有兴趣阅读这个丑陋的事情here

更多的是,它对rspec 2和rspec 3的作用不同。

rspec 2,完全符合要求:

    1) A json response is expected to have equal value at json["id"]
       Failure/Error: expect(value).to eq(json)

         expected: 37
              got: 25

         (compared using ==)
       # ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
       # ./lib/rspec/json_expectations/expectations.rb:32:in `block in generate_examples_for'

    2) A json response is expected to have equal value at json["name"]
       Failure/Error: expect(value).to eq(json)

         expected: "Smith"
              got: "John"

         (compared using ==)
       # ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
       # ./lib/rspec/json_expectations/expectations.rb:32:in `block in generate_examples_for'

rspec 3,略有偏差,但仍然可以接受:

故障:

{{1}}

以下是问题:

  • 是否有内置的公共API方法来实现它?
  • 看来我在这里命名有问题,不是期望,而是什么......?

1 个答案:

答案 0 :(得分:0)

所以,它现在有点难看(尽管第一次迭代),但是有效。不使用RSpec内部进行修改,仅使用公共RSpec API,具有良好的错误消息,甚至无需清除回溯:

module RSpec
  module JsonExpectations
    class JsonTraverser
      def self.traverse(errors, expected, actual, prefix=[])
        if expected.is_a?(Hash)

          expected.map do |key, value|
            new_prefix = prefix + [key]
            if actual.has_key?("#{key}")
              traverse(errors, value, actual["#{key}"], new_prefix)
            else
              errors[new_prefix.join("/")] = :no_key
              false
            end
          end.all?

        elsif expected.is_a?(String) || expected.is_a?(Numeric)

          if actual == expected
            true
          else
            errors[prefix.join("/")] = {
              actual: actual,
              expected: expected
            }
            false
          end

        else
          raise NotImplementedError, "#{expected} expectation is not supported"
        end
      end
    end
  end
end

RSpec::Matchers.define :include_json do |expected|
  match do |actual|
    unless expected.is_a?(Hash)
      raise ArgumentError, "Expected value must be a json for include_json matcher"
    end

    RSpec::JsonExpectations::JsonTraverser.traverse(
      @include_json_errors = {},
      expected,
      JSON.parse(actual)
    )
  end

  # RSpec 2 vs 3
  send(respond_to?(:failure_message) ?
       :failure_message :
       :failure_message_for_should) do |actual|
         res = []

         @include_json_errors.each do |json_path, error|
           res << %{
           json atom on path "#{json_path}" is missing
           } if error == :no_key

           res << %{
           json atom on path "#{json_path}" is not equal to expected value:

             expected: #{error[:expected].inspect}
                  got: #{error[:actual].inspect}
           } if error.is_a?(Hash) && error.has_key?(:expected)
         end

         res.join("")
       end

end

现在允许使用此语法:

        it "has basic info about user" do
          expect(subject).to include_json(
            id: 37,
            email: "john.smith@example.com",
            name: "Smith"
          )
        end

它会产生如下错误:

  F

  Failures:

    1) A json response has basic info about user
       Failure/Error: expect(subject).to include_json(

                    json atom on path "id" is not equal to expected value:

                      expected: 37
                           got: 25

                    json atom on path "name" is not equal to expected value:

                      expected: "Smith"
                           got: "John"

       # ./spec/simple_with_fail_spec.rb:11:in `block (2 levels) in <top (required)>'

  Finished in 0.00065 seconds
  1 example, 1 failure

  Failed examples:

  rspec ./spec/simple_with_fail_spec.rb:10 # A json response has basic info about user

适用于RSpec 2和3。

如果感兴趣的人在这里https://github.com/waterlink/rspec-json_expectations