RSpec का उपयोग करके JSON की प्रतिक्रिया कैसे जांचें?


145

मेरे नियंत्रक में निम्नलिखित कोड हैं:

format.json { render :json => { 
        :flashcard  => @flashcard,
        :lesson     => @lesson,
        :success    => true
} 

मेरे RSpec नियंत्रक परीक्षण में मैं यह सत्यापित करना चाहता हूं कि एक निश्चित परिदृश्य को एक सफलता की प्रतिक्रिया मिलती है, इसलिए मेरे पास निम्न पंक्ति थी:

controller.should_receive(:render).with(hash_including(:success => true))

हालाँकि जब मैं अपने परीक्षण चलाता हूँ तो मुझे निम्नलिखित त्रुटि मिलती है:

Failure/Error: controller.should_receive(:render).with(hash_including(:success => false))
 (#<AnnoController:0x00000002de0560>).render(hash_including(:success=>false))
     expected: 1 time
     received: 0 times

क्या मैं गलत तरीके से प्रतिक्रिया की जाँच कर रहा हूँ?

जवाबों:


164

आप प्रतिक्रिया ऑब्जेक्ट की जांच कर सकते हैं और सत्यापित कर सकते हैं कि इसमें अपेक्षित मूल्य है:

@expected = { 
        :flashcard  => @flashcard,
        :lesson     => @lesson,
        :success    => true
}.to_json
get :action # replace with action name / params as necessary
response.body.should == @expected

संपादित करें

इसे बदलने postसे यह थोड़ा मुश्किल हो जाता है। यहाँ इसे संभालने का एक तरीका दिया गया है:

 it "responds with JSON" do
    my_model = stub_model(MyModel,:save=>true)
    MyModel.stub(:new).with({'these' => 'params'}) { my_model }
    post :create, :my_model => {'these' => 'params'}, :format => :json
    response.body.should == my_model.to_json
  end

ध्यान दें कि mock_modelकोई प्रतिक्रिया नहीं देगा to_json, इसलिए या तो stub_modelएक वास्तविक मॉडल उदाहरण की आवश्यकता है।


1
मैंने यह कोशिश की और दुर्भाग्य से यह कहता है कि इसे "" की प्रतिक्रिया मिली। क्या यह नियंत्रक में त्रुटि हो सकती है?
फिज्ज़

इसके अलावा कार्रवाई 'बना' है, इससे कोई फर्क नहीं पड़ता कि मैं एक पोस्ट के बजाय एक पोस्ट का उपयोग करता हूं?
फिज्ज़

हाँ, आप post :createएक मान्य मानदंड हैश के साथ चाहते हैं ।
zetetic

4
आपको उस प्रारूप को भी निर्दिष्ट करना चाहिए जो आप अनुरोध कर रहे हैं। post :create, :format => :json
रॉबर्ट स्पाइचर

8
JSON केवल एक स्ट्रिंग है, वर्णों का एक क्रम और उनका क्रम मायने रखता है। {"a":"1","b":"2"}और {"b":"2","a":"1"}समान तार नहीं हैं जो समान वस्तुओं को नोट करते हैं। आपको स्ट्रिंग्स की तुलना नहीं बल्कि वस्तुओं की तुलना करनी चाहिए JSON.parse('{"a":"1","b":"2"}').should == {"a" => "1", "b" => "2"}
खोपड़ी

165

आप इस तरह प्रतिक्रिया निकाय को पार्स कर सकते हैं:

parsed_body = JSON.parse(response.body)

तब आप उस पार्स कंटेंट के खिलाफ अपने दावे कर सकते हैं।

parsed_body["foo"].should == "bar"

6
यह बहुत आसान लगता है। धन्यवाद।
tbaums

सबसे पहले, बहुत बहुत धन्यवाद। एक छोटा सुधार: JSON.parse (response.body) एक सरणी देता है। ['फू'] हालांकि हैश मान में एक कुंजी के लिए खोज करता है। सही किया गया व्यक्ति पार्स_बॉडी है [0] ['फू']।
Caneyeylan

5
JSON.parse केवल एक सरणी देता है यदि JSON स्ट्रिंग में एक सरणी थी।
redjohn

2
@PriyankaK यदि यह HTML लौटा रहा है, तो आपकी प्रतिक्रिया json नहीं है। सुनिश्चित करें कि आपका अनुरोध json प्रारूप निर्दिष्ट कर रहा है।
brentmc79

10
आप भी उपयोग b = JSON.parse(response.body, symoblize_names: true)कर सकते हैं ताकि आप उन्हें प्रतीकों का उपयोग करके एक्सेस कर सकें जैसे:b[:foo]
फ्लोटिंगरॉक

45

के बंद का निर्माण केविन ट्रोब्रिज के जवाब

response.header['Content-Type'].should include 'application/json'

21
rspec-rails इसके लिए एक माचिस प्रदान करता है: अपेक्षा (response.content_type) .to eq ("application / json")
Dan Garland

4
क्या आप Mime::JSONइसके बजाय सिर्फ उपयोग नहीं कर सकते 'application/json'?
फ्लोटिंगरॉक

@FloatingRock मुझे लगता है कि आपको जरूरत होगीMime::JSON.to_s
एडगर ओर्टेगा


13

यह करने के लिए सरल और आसान तरीका है।

# set some variable on success like :success => true in your controller
controller.rb
render :json => {:success => true, :data => data} # on success

spec_controller.rb
parse_json = JSON(response.body)
parse_json["success"].should == true

11

आप एक सहायक फ़ंक्शन को अंदर भी परिभाषित कर सकते हैं spec/support/

module ApiHelpers
  def json_body
    JSON.parse(response.body)
  end
end

RSpec.configure do |config| 
  config.include ApiHelpers, type: :request
end

और json_bodyजब भी आपको JSON प्रतिक्रिया का उपयोग करने की आवश्यकता हो, उसका उपयोग करें।

उदाहरण के लिए, आपके अनुरोध के अंदर आप इसे सीधे उपयोग कर सकते हैं

context 'when the request contains an authentication header' do
  it 'should return the user info' do
    user  = create(:user)
    get URL, headers: authenticated_header(user)

    expect(response).to have_http_status(:ok)
    expect(response.content_type).to eq('application/vnd.api+json')
    expect(json_body["data"]["attributes"]["email"]).to eq(user.email)
    expect(json_body["data"]["attributes"]["name"]).to eq(user.name)
  end
end

8

सिर्फ JSON प्रतिक्रिया के लिए परीक्षण करने के लिए एक और दृष्टिकोण (नहीं कि सामग्री में अपेक्षित मूल्य शामिल है), ActiveSupport का उपयोग करके प्रतिक्रिया को पार्स करना है:

ActiveSupport::JSON.decode(response.body).should_not be_nil

यदि प्रतिक्रिया पार्स करने योग्य नहीं है, तो JSON एक अपवाद फेंक दिया जाएगा और परीक्षण विफल हो जाएगा।


7

आप 'Content-Type'शीर्ष लेख में देख सकते हैं कि यह सही है?

response.header['Content-Type'].should include 'text/javascript'

1
के लिए render :json => object, मेरा मानना है कि रेल रिटर्न 'आवेदन / json' का एक Content-Type हैडर।
21

1
सबसे अच्छा विकल्प मुझे लगता है:response.header['Content-Type'].should match /json/
ईंट

यह पसंद है क्योंकि यह चीजों को सरल रखता है और एक नई निर्भरता नहीं जोड़ता है।
वेबपाप्य

5

रेल 5 का उपयोग करते समय (वर्तमान में अभी भी बीटा में), parsed_bodyपरीक्षण प्रतिक्रिया पर एक नई विधि है , जो अंतिम अनुरोध के रूप में एन्कोड किए गए प्रतिक्रिया को पार्स कर देगा।

GitHub पर प्रतिबद्ध: https://github.com/rails/rails/commit/eee3534b


रेल 5 ने इसे बीटा से बाहर कर दिया, साथ #parsed_body। यह अभी तक प्रलेखित नहीं है, लेकिन कम से कम JSON प्रारूप काम करता है। ध्यान दें कि कुंजी अभी भी तार (प्रतीकों के बजाय) हैं, इसलिए कोई भी #deep_symbolize_keysया #with_indifferent_accessउपयोगी हो सकता है (मुझे बाद की पसंद है)।
फ्रैंकलिन यू

1

यदि आप हैश डिफरेंस का लाभ लेना चाहते हैं तो Rspec प्रदान करता है, शरीर को पार्स करना और हैश के खिलाफ तुलना करना बेहतर है। सबसे सरल तरीका जो मैंने पाया है:

it 'asserts json body' do
  expected_body = {
    my: 'json',
    hash: 'ok'
  }.stringify_keys

  expect(JSON.parse(response.body)).to eql(expected_body)
end

1

JSON तुलना समाधान

पैदावार एक साफ लेकिन संभावित रूप से बड़ी मुश्किल:

actual = JSON.parse(response.body, symbolize_names: true)
expected = { foo: "bar" }
expect(actual).to eq expected

वास्तविक डेटा से कंसोल आउटपुट का उदाहरण:

expected: {:story=>{:id=>1, :name=>"The Shire"}}
     got: {:story=>{:id=>1, :name=>"The Shire", :description=>nil, :body=>nil, :number=>1}}

   (compared using ==)

   Diff:
   @@ -1,2 +1,2 @@
   -:story => {:id=>1, :name=>"The Shire"},
   +:story => {:id=>1, :name=>"The Shire", :description=>nil, ...}

(@Floatingrock द्वारा टिप्पणी करने के लिए धन्यवाद)

स्ट्रिंग तुलना समाधान

यदि आप एक लौह-क्लैड समाधान चाहते हैं, तो आपको उन पार्सर का उपयोग करने से बचना चाहिए जो झूठी सकारात्मक समानता का परिचय दे सकते हैं; एक स्ट्रिंग के खिलाफ प्रतिक्रिया शरीर की तुलना करें। उदाहरण के लिए:

actual = response.body
expected = ({ foo: "bar" }).to_json
expect(actual).to eq expected

लेकिन यह दूसरा समाधान कम नेत्रहीन रूप से अनुकूल है क्योंकि यह क्रमबद्ध JSON का उपयोग करता है जिसमें बहुत सारे बच गए उद्धरण चिह्न शामिल होंगे।

कस्टम मिलान समाधान

मैं खुद को एक कस्टम मैचर लिखने की कोशिश करता हूं, जो जेन्सन पथ अलग हो सकता है, जो पुनरावर्ती स्लॉट पर बिल्कुल बेहतर काम करता है। निम्नलिखित को अपने rspec मैक्रो में जोड़ें:

def expect_response(actual, expected_status, expected_body = nil)
  expect(response).to have_http_status(expected_status)
  if expected_body
    body = JSON.parse(actual.body, symbolize_names: true)
    expect_json_eq(body, expected_body)
  end
end

def expect_json_eq(actual, expected, path = "")
  expect(actual.class).to eq(expected.class), "Type mismatch at path: #{path}"
  if expected.class == Hash
    expect(actual.keys).to match_array(expected.keys), "Keys mismatch at path: #{path}"
    expected.keys.each do |key|
      expect_json_eq(actual[key], expected[key], "#{path}/:#{key}")
    end
  elsif expected.class == Array
    expected.each_with_index do |e, index|
      expect_json_eq(actual[index], expected[index], "#{path}[#{index}]")
    end
  else
    expect(actual).to eq(expected), "Type #{expected.class} expected #{expected.inspect} but got #{actual.inspect} at path: #{path}"
  end
end

उपयोग 1 का उदाहरण:

expect_response(response, :no_content)

उपयोग 2 का उदाहरण:

expect_response(response, :ok, {
  story: {
    id: 1,
    name: "Shire Burning",
    revisions: [ ... ],
  }
})

उदाहरण आउटपुट:

Type String expected "Shire Burning" but got "Shire Burnin" at path: /:story/:name

एक नेस्टेड सरणी में एक बेमेल गहरी प्रदर्शित करने के लिए एक और उदाहरण आउटपुट:

Type Integer expected 2 but got 1 at path: /:story/:revisions[0]/:version

जैसा कि आप देख सकते हैं, आउटपुट आपको बताता है कि आपके अपेक्षित JSON को ठीक करने के लिए कहां है।


0

मुझे यहाँ एक ग्राहक मिलान मिला: https://raw.github.com/gist/917903/92d7101f643e07896659f84609c117c4c279dfad/have_content_type.rb

इसे स्पेक / सपोर्ट / मैचर्स / have_content_type.rb में डालें और कुछ इस तरह से आप के साथ सपोर्ट से सामान लोड करना सुनिश्चित करें।

Dir[Rails.root.join('spec/support/**/*.rb')].each {|f| require f}

यहां कोड ही है, बस दिए गए लिंक से गायब होने की स्थिति में।

RSpec::Matchers.define :have_content_type do |content_type|
  CONTENT_HEADER_MATCHER = /^(.*?)(?:; charset=(.*))?$/

  chain :with_charset do |charset|
    @charset = charset
  end

  match do |response|
    _, content, charset = *content_type_header.match(CONTENT_HEADER_MATCHER).to_a

    if @charset
      @charset == charset && content == content_type
    else
      content == content_type
    end
  end

  failure_message_for_should do |response|
    if @charset
      "Content type #{content_type_header.inspect} should match #{content_type.inspect} with charset #{@charset}"
    else
      "Content type #{content_type_header.inspect} should match #{content_type.inspect}"
    end
  end

  failure_message_for_should_not do |model|
    if @charset
      "Content type #{content_type_header.inspect} should not match #{content_type.inspect} with charset #{@charset}"
    else
      "Content type #{content_type_header.inspect} should not match #{content_type.inspect}"
    end
  end

  def content_type_header
    response.headers['Content-Type']
  end
end

0

उपरोक्त बहुत से उत्तर थोड़े से पुराने हैं, इसलिए यह RSpec (3.8+) के अधिक हाल के संस्करण के लिए एक त्वरित सारांश है। यह समाधान रुबोकॉप-आरस्पेक से कोई चेतावनी नहीं देता है और आरएसईपीसी सर्वोत्तम प्रथाओं के साथ इनलाइन है :

एक सफल JSON प्रतिक्रिया को दो चीजों से पहचाना जाता है:

  1. प्रतिक्रिया का सामग्री प्रकार है application/json
  2. प्रतिक्रिया का शरीर त्रुटियों के बिना पार्स किया जा सकता है

यह मानते हुए कि प्रतिक्रिया वस्तु परीक्षण का अनाम विषय है, उपरोक्त दोनों ही स्थिति Rspec द्वारा निर्मित माचिस के उपयोग से मान्य की जा सकती है:

context 'when response is received' do
  subject { response }

  # check for a successful JSON response
  it { is_expected.to have_attributes(content_type: include('application/json')) }
  it { is_expected.to have_attributes(body: satisfy { |v| JSON.parse(v) }) }

  # validates OP's condition
  it { is_expected.to satisfy { |v| JSON.parse(v.body).key?('success') }
  it { is_expected.to satisfy { |v| JSON.parse(v.body)['success'] == true }
end

यदि आप अपने विषय को नाम देने के लिए तैयार हैं तो उपरोक्त परीक्षणों को और सरल बनाया जा सकता है:

context 'when response is received' do
  subject(:response) { response }

  it 'responds with a valid content type' do
    expect(response.content_type).to include('application/json')
  end

  it 'responds with a valid json object' do
    expect { JSON.parse(response.body) }.not_to raise_error
  end

  it 'validates OPs condition' do
    expect(JSON.parse(response.body, symoblize_names: true))
      .to include(success: true)
  end
end
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.