RSpec을 사용하여 JSON 응답을 확인하는 방법은 무엇입니까?


145

컨트롤러에 다음 코드가 있습니다.

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

내 RSpec 컨트롤러 테스트에서 특정 시나리오가 성공 json 응답을 수신하는지 확인하여 다음 줄을 가지고 싶습니다.

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
나는 이것을 시도했지만 불행히도 ""의 응답을 받았다고 말합니다. 컨트롤러에 오류가있을 수 있습니까?
Fizz

또한 작업은 '만들기'입니다. get 대신 게시물을 사용하는 것보다 중요합니까?
Fizz

예, post :create유효한 매개 변수 해시를 원할 것 입니다.
zetetic

4
요청하는 형식도 지정해야합니다. post :create, :format => :json
Robert Speicher

8
JSON은 문자열, 일련의 문자 및 순서 만 중요합니다. {"a":"1","b":"2"}{"b":"2","a":"1"}동일한 목적을 표기하기 동일한 문자열이되지 않는다. 문자열과 객체를 비교하지 JSON.parse('{"a":"1","b":"2"}').should == {"a" => "1", "b" => "2"}말고 대신 수행하십시오.
skalee 2016 년

165

다음과 같이 응답 본문을 구문 분석 할 수 있습니다.

parsed_body = JSON.parse(response.body)

그런 다음 구문 분석 된 컨텐츠에 대해 어설 션을 작성할 수 있습니다.

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

6
이것은 훨씬 쉬워 보인다 . 감사.
tbaums

먼저 감사합니다. 작은 수정 : JSON.parse (response.body)는 배열을 반환합니다. [ 'foo'] 그러나 해시 값에서 키를 검색합니다. 정정 된 것은 parsed_body [0] [ 'foo']입니다.
CanCeylan

5
JSON.parse는 JSON 문자열에 배열이있는 경우에만 배열을 반환합니다.
redjohn

2
@PriyankaK HTML을 반환하면 응답이 json이 아닙니다. 요청이 json 형식을 지정하고 있는지 확인하십시오.
brentmc79

10
다음 b = JSON.parse(response.body, symoblize_names: true)과 같은 기호를 사용하여 액세스 할 수 있도록 사용할 수도 있습니다 .b[:foo]
FloatingRock



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_bodyJSON 응답에 액세스해야 할 때마다 사용 하십시오.

예를 들어 요청 사양 내에서 직접 사용할 수 있습니다.

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 => objectRails는 'application / json'의 Content-Type 헤더를 반환한다고 생각합니다.
lightyrs

1
내가 생각하는 최고의 옵션 :response.header['Content-Type'].should match /json/
bricker

그것은 일을 단순하게 유지하고 새로운 의존성을 추가하지 않기 때문에 좋아합니다.
webpapaya

5

Rails 5 (현재는 아직 베타 버전) parsed_body를 사용하는 경우 테스트 응답에 새로운 방법이 있습니다.이 메소드 는 마지막 요청이 인코딩 된 것으로 해석 된 응답을 반환합니다.

GitHub의 커밋 : https://github.com/rails/rails/commit/eee3534b


Rails 5는와 함께 베타 버전으로 만들었습니다 #parsed_body. 아직 문서화되어 있지 않지만 적어도 JSON 형식이 작동합니다. 키는 여전히 기호가 아닌 문자열이므로 찾을 수 #deep_symbolize_keys있거나 #with_indifferent_access유용 할 수 있습니다 (후자가 마음에 듭니다).
Franklin Yu

1

Rspec이 제공하는 해시 diff를 활용하려면 본문을 구문 분석하고 해시와 비교하는 것이 좋습니다. 내가 찾은 가장 간단한 방법 :

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 비교 솔루션

깨끗하지만 잠재적으로 큰 Diff를 생성합니다.

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을 사용하므로 시각적으로 친숙하지 않습니다.

맞춤형 매처 솔루션

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 에서 고객 매처를 찾았습니다.

spec / support / matchers / have_content_type.rb에 넣고 spec / spec_helper.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+)에 대한 간단한 요약입니다. 이 솔루션은 rubocop-rspec 에서 경고를 발생 시키지 않으며 rspec 모범 사례 와 일치 합니다 .

성공적인 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.