Rswag: Question: multiple response for multiple parameters

Created on 20 Jul 2017  路  4Comments  路  Source: rswag/rswag

I have the following spec test:

require 'swagger_helper'

describe 'Devices API' do

  path '/api/v1/devices' do

    get 'Retrieves devices, optionally mating criteria' do
      security [ Token: [] ]
      produces 'application/json'

      let(:api_key) { FactoryGirl.create(:api_key) }
      let(:Authorization) { "Token token=\"#{api_key.access_token}\"" }

      before do |example|
        loc = FactoryGirl.create(:location, name: 'Earth')
        FactoryGirl.create(:device, location: loc)
        FactoryGirl.create(:device, location: loc)
        FactoryGirl.create(:device, location: loc)
        submit_request(example.metadata)
      end

      response '200', 'Returns all Earth devices' do
        parameter name: :location, :in => :query, :type => :string
        let(:location) { 'Earth' }

        it 'returns a valid 201 response' do |example|
          assert_response_matches_metadata(example.metadata)
        end

        it 'returns an array with 3 entries' do |example|
          data = JSON.parse(response.body)
          p data
          expect(data.class).to be(Array)
          expect(data.size).to eq(3)
        end
      end

      response '200', 'no devices found' do
        parameter name: :role, :in => :query, :type => :string
        let(:role) { 'DoesNotExist' }

        before do |example|
          submit_request(example.metadata)
        end

        examples 'application/json' => {}

        it 'returns an array with 0 entries' do |example|
          data = JSON.parse(response.body)
          expect(data.class).to be(Array)
          expect(data.size).to eq(0)
        end

      end
    end
  end

end

I'm failing to understand how parameters work in this case. My api method call takes three optional parameters that I would like to test independently, but in each response block, if I add a parameter to it, that parameter seems to be required in all other response blocks. So if a parameter shows up in any response block, its appended to the call for every response block., which is not what I want.

I'm looking for a way that I can independently test a parameter in isolation, but have all parameters noted in the API docs (and not duplicated). If anyone has advice on the subject, I would appreciate it.

Most helpful comment

You need to describe the operation fully, including optional parameters, before the response blocks. Unlike the declarations that come before it, a response block is a specific test case where you provide an expected result (e.g. status code) and then assign parameter values (via let) to yield that result. There should be only 1 response block per status code but you can use contexts within them to create sub test cases for a given status code. For example:

get 'Retrieves devices, optionally mating criteria' do
  produces 'application/json'
  parameter name: :location, in: :query, type: 'string', required: false
  parameter role: :location, in: :query, type: 'string', required: false

  response '200', 'success' do

    context 'location provided' do
      let(:location) { 'Earth' }
      before do
        submit_request(example.metadata)
      end

      it 'returns devices matching the provided location' do
        data = JSON.parse(response.body)
        expect(data.size).to eq(3)
      end
    end

    context 'role provided' do
      let(:role) { 'DoesNotExist' }
      before do
        submit_request(example.metadata)
      end

      it 'returns devices matching the provided role' do
        data = JSON.parse(response.body)
        expect(data.size).to eq(0)
      end
    end
  end
end

All 4 comments

You need to describe the operation fully, including optional parameters, before the response blocks. Unlike the declarations that come before it, a response block is a specific test case where you provide an expected result (e.g. status code) and then assign parameter values (via let) to yield that result. There should be only 1 response block per status code but you can use contexts within them to create sub test cases for a given status code. For example:

get 'Retrieves devices, optionally mating criteria' do
  produces 'application/json'
  parameter name: :location, in: :query, type: 'string', required: false
  parameter role: :location, in: :query, type: 'string', required: false

  response '200', 'success' do

    context 'location provided' do
      let(:location) { 'Earth' }
      before do
        submit_request(example.metadata)
      end

      it 'returns devices matching the provided location' do
        data = JSON.parse(response.body)
        expect(data.size).to eq(3)
      end
    end

    context 'role provided' do
      let(:role) { 'DoesNotExist' }
      before do
        submit_request(example.metadata)
      end

      it 'returns devices matching the provided role' do
        data = JSON.parse(response.body)
        expect(data.size).to eq(0)
      end
    end
  end
end

Thanks for the response. I'd initially thought that's how it worked, but I keep running into an issue. Here is an updated spec and the failure I see. Note the commented let items are those throwing the rspec errors below. I've also removed bits from the test you likely don't care about.

    get 'Retrieves devices, optionally mating criteria' do
      produces 'application/json'

      parameter name: :location, in: :query, type: 'string', required: false
      parameter name: :model, in: :query, type: 'string', required: false
      parameter name: :role, in: :query, type: 'string', required: false

      response '200', 'Returns all Earth devices' do

        before do |example|
          submit_request(example.metadata)
        end

        context 'location provided' do
          let(:location) { 'Earth' }
        # let(:model) { 'Human' }
        # let(:role) { 'Think' }

          it 'returns an array with 3 entries' do |example|
            data = JSON.parse(response.body)
            expect(data.class).to be(Array)
            expect(data.size).to eq(3)
          end
        end

      end
    end

The failure for this test is as follows.

  1) Devices API /devices get Returns all Earth devices location provided returns an array with 3 entries
     Failure/Error: submit_request(example.metadata)

     NoMethodError:
       undefined method `model' for #<RSpec::ExampleGroups::DevicesAPI::Devices::Get::ReturnsAllEarthDevices::LocationProvided:0x007f88c44469a8>
     # ./spec/integration/devices_spec.rb:28:in `block (5 levels) in <top (required)>'
     # ./spec/spec_helper.rb:80:in `block (3 levels) in <top (required)>'
     # ./spec/spec_helper.rb:79:in `block (2 levels) in <top (required)>'

If I instead comment out the let for location, I get a different error.

  1) Devices API /devices get Returns all Earth devices location provided returns an array with 3 entries
     Failure/Error: submit_request(example.metadata)
       `location` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).
     # ./spec/integration/devices_spec.rb:28:in `block (5 levels) in <top (required)>'
     # ./spec/spec_helper.rb:80:in `block (3 levels) in <top (required)>'
     # ./spec/spec_helper.rb:79:in `block (2 levels) in <top (required)>'

Which version of rswag? You'll need latest - 1.5.0

Ah, much better. Updating to 1.5.0 has corrected both rspec errors above. Thank you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nicwillemse picture nicwillemse  路  4Comments

bspellacy picture bspellacy  路  3Comments

ManevilleF picture ManevilleF  路  4Comments

mcoms picture mcoms  路  4Comments

anjum-ahmed picture anjum-ahmed  路  6Comments