RSpec kullanarak JSON yanıtı nasıl kontrol edilir?


147

Denetleyicimde aşağıdaki kod var:

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

RSpec denetleyici testimde, belirli bir senaryonun başarılı bir json yanıtı aldığını doğrulamak istiyorum, bu nedenle aşağıdaki satırı elde ettim:

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

Testlerimi çalıştırdığımda şu hatayı alıyorum:

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

Yanıtı yanlış mı kontrol ediyorum?

Yanıtlar:


165

Yanıt nesnesini inceleyebilir ve beklenen değeri içerdiğini doğrulayabilirsiniz:

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

DÜZENLE

postBunu a olarak değiştirmek , işi biraz daha zor hale getirir. İşte bunu halletmenin bir yolu:

 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_modelBunun yanıt vermeyeceğini unutmayın to_json, bu nedenle ya stub_modelda gerçek bir model örneğine ihtiyaç vardır.


1
Bunu denedim ve maalesef "" yanıtını aldığını söylüyor. Bu, denetleyicide bir hata olabilir mi?
Fizz

Ayrıca eylem 'oluştur', almak yerine gönderi kullanmamın önemi var mı?
Fizz

Evet, post :creategeçerli bir parametre karması ile istersiniz .
zetetic

4
Ayrıca talep ettiğiniz biçimi de belirtmelisiniz. post :create, :format => :json
Robert Speicher

8
JSON yalnızca bir dizedir, bir dizi karakterdir ve bunların sırası önemlidir. {"a":"1","b":"2"}ve {"b":"2","a":"1"}eşit nesneleri not eden eşit dizeler değildir. Dizeleri değil nesneleri karşılaştırmalısınız, JSON.parse('{"a":"1","b":"2"}').should == {"a" => "1", "b" => "2"}bunun yerine yapın.
skalee

166

Yanıt gövdesini şu şekilde ayrıştırabilirsiniz:

parsed_body = JSON.parse(response.body)

Ardından, bu ayrıştırılmış içeriğe karşı iddialarınızı yapabilirsiniz.

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

6
bu çok daha kolay görünüyor . Teşekkürler.
tbaums

Öncelikle çok teşekkürler. Küçük bir düzeltme: JSON.parse (response.body) bir dizi döndürür. ['foo'] ancak karma değerde bir anahtar arar. Düzeltilmiş olan, parsed_body [0] ['foo'] 'dur.
CanCeylan

5
JSON.parse, yalnızca JSON dizesinde bir dizi varsa bir dizi döndürür.
redjohn

2
@ PriyankaK HTML döndürüyorsa, cevabınız json değil. İsteğinizin json formatını belirttiğinden emin olun.
brentmc79

10
b = JSON.parse(response.body, symoblize_names: true)b[:foo]
FloatingRock



13

Bunu yapmanın basit ve kolay yolu.

# 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

Ayrıca içinde bir yardımcı işlev tanımlayabilirsiniz. spec/support/

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

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

ve kullanım json_bodyJSON yanıtını erişmek gerektiğinde.

Örneğin, istek spesifikasyonunuzun içinde doğrudan kullanabilirsiniz

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

Yalnızca bir JSON yanıtını test etmek için başka bir yaklaşım (içindeki içeriğin beklenen bir değer içermesi değil), yanıtı ActiveSupport kullanarak ayrıştırmaktır:

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

Yanıt çözümlenebilir JSON değilse, bir istisna atılır ve test başarısız olur.


7

'Content-Type'Doğru olup olmadığını görmek için başlığa bakabilir misin?

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

1
Çünkü render :json => object, Rails'in 'application / json' içerik türü başlığını döndürdüğüne inanıyorum.
lightyrs

1
Bence en iyi seçenek:response.header['Content-Type'].should match /json/
bricker

Sevin çünkü işleri basit tutuyor ve yeni bir bağımlılık eklemiyor.
webpapaya

5

Rails 5 kullanırken (şu anda beta sürümündedir), parsed_bodytest yanıtında, son isteğin kodlandığı şekilde ayrıştırılan yanıtı döndürecek yeni bir yöntem vardır .

GitHub'daki taahhüt: https://github.com/rails/rails/commit/eee3534b


Rails 5, beta sürümünden çıktı #parsed_body. Henüz belgelenmedi, ancak en azından JSON formatı çalışıyor. Unutmayın ki anahtarlar hala dizelerdir (semboller yerine), bu yüzden biri faydalı #deep_symbolize_keysveya ikisinden birini bulabilir #with_indifferent_access(ikincisini seviyorum).
Franklin Yu

2

JSON karşılaştırma çözümü

Temiz ancak potansiyel olarak büyük bir Diff verir:

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

Gerçek verilerden konsol çıktısı örneği:

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 tarafından yapılan yorum için teşekkürler)

Dize karşılaştırma çözümü

Demir kaplı bir çözüm istiyorsanız, yanlış pozitif eşitliğe yol açabilecek ayrıştırıcılar kullanmaktan kaçınmalısınız; yanıt gövdesini bir dizeyle karşılaştırın. Örneğin:

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

Ancak bu ikinci çözüm, birçok çıkış karakterli tırnak işareti içeren serileştirilmiş JSON kullandığından daha az görsel dostudur.

Özel eşleştirici çözümü

Kendime, JSON yollarının tam olarak hangi yinelemeli yuvada farklı olduğunu saptamak için çok daha iyi bir iş çıkaran özel bir eşleştirici yazma eğilimindeyim. Aşağıdakileri rspec makrolarınıza ekleyin:

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

Kullanım 1 örneği:

expect_response(response, :no_content)

Kullanım 2 örneği:

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

Örnek çıktı:

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

İç içe geçmiş bir dizide derin bir uyuşmazlığı göstermek için başka bir örnek çıktı:

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

Gördüğünüz gibi, çıktı size tam olarak beklenen JSON'unuzu nerede düzelteceğinizi söyler.


1

Rspec'in sağladığı hash farkından yararlanmak istiyorsanız, gövdeyi ayrıştırıp bir hash ile karşılaştırmak daha iyidir. Bulduğum en basit yol:

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

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

0

Burada bir müşteri eşleştirici buldum: https://raw.github.com/gist/917903/92d7101f643e07896659f84609c117c4c279dfad/have_content_type.rb

Bunu spec / support / matchers / have_content_type.rb içine koyun ve spec / spec_helper.rb dosyasında buna benzer bir şeyle destekten öğeler yüklediğinizden emin olun.

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

Verilen bağlantıdan kaybolması ihtimaline karşı kodun kendisi.

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

Yukarıdaki cevapların çoğu biraz güncel değil, bu nedenle bu RSpec'in (3.8+) daha yeni bir sürümü için hızlı bir özet. Bu çözüm, rubocop-rspec'ten hiçbir uyarı getirmez ve rspec en iyi uygulamaları ile uyumludur :

Başarılı bir JSON yanıtı iki şeyle tanımlanır:

  1. Yanıtın içerik türü application/json
  2. Yanıtın gövdesi hatasız olarak ayrıştırılabilir

Yanıt nesnesinin testin anonim konusu olduğu varsayıldığında, yukarıdaki koşulların her ikisi de Rspec'in yerleşik eşleştiricileri kullanılarak doğrulanabilir:

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

Konunuzu adlandırmaya hazırsanız, yukarıdaki testler daha da basitleştirilebilir:

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

0

JSON yanıtınız için bu yanıtı beklenen sonuçlar için ayrıştırmalısınız Örnek İçin: parsed_response = JSON.parse(response.body)

Yanıtta bulunan diğer değişkenleri kontrol edebilirsiniz.

expect(parsed_response["success"]).to eq(true)
expect(parsed_response["flashcard"]).to eq("flashcard expected value")
expect(parsed_response["lesson"]).to eq("lesson expected value")
expect(subject["status_code"]).to eq(201)

JSON yanıtının anahtarlarını da kontrol etmeyi tercih ederim, Örneğin:

expect(body_as_json.keys).to match_array(["success", "lesson","status_code", "flashcard"])

Burada, Rspec'te beklenen sonuçlar için omuz eşleştiricileri kullanabiliriz

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.