我正在尝试创建帮助程序以统一和好的方式测试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内部控制来修复,即:
IGNORE_REGEX
并再次定义它将此辅助文件添加到此正则表达式 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}}
以下是问题:
答案 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