I'm assuming this is a limitation of Swagger, however when I write a spec like this, with multiple response "422" blocks:
require "swagger_helper"
describe "User registration API" do
let!(:user) { FactoryBot.create(:user) }
let(:Authorization) { "Bearer #{::InternalApi::UserToken.encode(user)}" }
path '/users/registrations' do
patch "updates user email and/or password" do
security [ bearer: [] ]
consumes 'application/json'
parameter name: :body, in: :body, required: true, schema: {
type: :object,
properties: {
current_password: { type: :string },
password: { type: :string },
email: { type: :string },
},
required: [ "current_password" ]
}
response "200", "email and/or password updated" do
let(:body) {{
current_password: FactoryBot.attributes_for(:user)[:password],
password: "newpassword",
email: "[email protected]",
}}
run_test!
end
response "422", "incorrect current password" do
let(:body) {{
current_password: "wrongpw",
password: "newpassword",
}}
before do |example|
submit_request(example.metadata)
end
it "returns expected response" do |example|
assert_response_matches_metadata(example.metadata)
expect(response.body).to eq('{"error":"Current password is invalid"}')
end
end
response "422", "invalid email" do
let(:body) {{
current_password: FactoryBot.attributes_for(:user)[:password],
email: "bademail",
}}
before do |example|
submit_request(example.metadata)
end
it "returns expected response" do |example|
assert_response_matches_metadata(example.metadata)
expect(response.body).to eq('{"error":"Email is invalid"}')
end
end
end
end
end
My 3 specs pass with Rspec just fine, however the generated Swagger UI documentation only references the final 422 definition for "invalid email":

Since it seems like a Swagger restriction, I switched to using context blocks under a single response block, which works just fine:
require "swagger_helper"
describe "User registration API" do
let!(:user) { FactoryBot.create(:user) }
let(:Authorization) { "Bearer #{::InternalApi::UserToken.encode(user)}" }
path '/users/registrations' do
patch "updates user email and/or password" do
security [ bearer: [] ]
consumes 'application/json'
parameter name: :body, in: :body, required: true, schema: {
type: :object,
properties: {
current_password: { type: :string },
password: { type: :string },
email: { type: :string },
},
required: [ "current_password" ]
}
response "200", "email and/or password updated" do
let(:body) {{
current_password: FactoryBot.attributes_for(:user)[:password],
password: "newpassword",
email: "[email protected]",
}}
run_test!
end
response "422", "incorrect current password or invalid email" do
context "incorrect current password" do
let(:body) {{
current_password: "wrongpw",
password: "newpassword",
}}
before do |example|
submit_request(example.metadata)
end
it "returns expected response" do |example|
assert_response_matches_metadata(example.metadata)
expect(response.body).to eq('{"error":"Current password is invalid"}')
end
end
context "invalid email" do
let(:body) {{
current_password: FactoryBot.attributes_for(:user)[:password],
email: "bademail",
}}
before do |example|
submit_request(example.metadata)
end
it "returns expected response" do |example|
assert_response_matches_metadata(example.metadata)
expect(response.body).to eq('{"error":"Email is invalid"}')
end
end
end
end
end
end
However maybe rswag should raise an error if multiple response codes are defined for the same method, to anticipate dumb dumbs like me? Just a thought.
Thanks for this gem btw, I like this test-integrated approach a lot better than annotating the controllers like other gems do!
You can't have multiple responses for the same response code because swagger.json uses the response code as a key by default. You can get around this by adding an after hook to the example which assigns the response to a new key, e.g:
after do |example|
example.metadata[:response][:examples] = {
# make sure the key here is unique
'wrong password' => JSON.parse(response.body, symbolize_names: true)
}
end
You can also hack around the unique key issue by adding whitespace to the response code and explicitly matching the response when whitespace is added, but this leads to misordered results.
response "200", "Found stuff" do
before do |example|
submit_request(example.metadata)
end
it 'returns 200' do
expect(status).to eq(200)
end
end
# note the space after 200
response "200 ", "Found other stuff" do
before do |example|
submit_request(example.metadata)
end
it 'actually returns 200, not "200 "' do
expect(status).to eq(200)
end
end
You can handle this lots of different ways. You could also use the oneOf key. It seems the right way to handle this has been a subject of debate in both swagger and openapi for a while though. Personally I prefer using the examples after hook.
https://github.com/OAI/OpenAPI-Specification/issues/270
https://github.com/swagger-api/swagger-ui/issues/3803
Not sure about throwing an error, I think the warning may be a better option here.
If you'd like to demonstrate different variants for one response code you can use examples for that
response 422, 'failure' do
examples 'application/json' => [
{
key: 'is_missing',
message: 'is missing',
payload: { path: 'contact.legal_agreement' },
type: 'params'
}, {
key: 'has_already_been_taken',
message: 'has already been taken',
payload: { path: 'contact.login' },
type: 'params'
}, {
key: 'is_in_invalid_format',
message: 'is in invalid format',
payload: { path: 'contact.name' },
type: 'params'
}, {
key: 'length_must_be_within',
message: 'length must be within 8 - 20',
payload: {
path: 'contact.password',
range: ['8', '20']
},
type: 'params'
}
]
context 'required field is missing' do
end
context 'user is already registered' do
end
context 'contact name is in invalid format' do
end
context 'contact password is too short' do
end
end
An advantage of that is that It will show all variants in the UI
It's breaking the standard slightly in that the key should be the MIME type but I did this for now. Hopefully OAPI v3 supports this better.
after do |example|
example.metadata[:response][:examples] = {
example.metadata[:example_group][:description] => JSON.parse(response.body, symbolize_names: true)
}
end
response 422, "invalid parameters" do
context "field A was missing" do
let(:params) { { b: "foo" } }
run_test!
end
context "field B was missing" do
let(:params) { { a: "bar" } }
run_test!
end
end
You could try
after do |example|
example.metadata[:response][:content] = {
'application/json' => {
examples: {
example.metadata[:example_group][:description] => {
value: JSON.parse(response.body, symbolize_names: true)
}
}
}
}
end
response 422, "invalid parameters" do
context "field A was missing" do
let(:params) { { b: "foo" } }
run_test!
end
context "field B was missing" do
let(:params) { { a: "bar" } }
run_test!
end
end
Most helpful comment
You can't have multiple responses for the same response code because swagger.json uses the response code as a key by default. You can get around this by adding an after hook to the example which assigns the response to a new key, e.g:
You can also hack around the unique key issue by adding whitespace to the response code and explicitly matching the response when whitespace is added, but this leads to misordered results.
You can handle this lots of different ways. You could also use the oneOf key. It seems the right way to handle this has been a subject of debate in both swagger and openapi for a while though. Personally I prefer using the examples after hook.
https://github.com/OAI/OpenAPI-Specification/issues/270
https://github.com/swagger-api/swagger-ui/issues/3803