assert response body based on spec examples

This commit is contained in:
domaindrivendev 2016-02-23 00:30:10 -08:00
parent 071239727a
commit 9ad7a49e6d
16 changed files with 218 additions and 59 deletions

View File

@ -13,6 +13,7 @@ gemspec
# To use a debugger
# gem 'debugger', group: [:development, :test]
gem 'sqlite3'
gem 'pry'
gem 'rspec-rails'
gem 'generator_spec'

View File

@ -105,6 +105,7 @@ GEM
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.11)
thor (0.19.1)
tilt (1.4.1)
treetop (1.4.15)
@ -119,4 +120,5 @@ DEPENDENCIES
generator_spec
pry
rspec-rails
sqlite3
swagger_rails!

View File

@ -1,13 +1,12 @@
module SwaggerRails
class TestDataBuilder
class TestCaseBuilder
def initialize(path_template, http_method, swagger)
@path_template = path_template
@http_method = http_method
@swagger = swagger
@param_values = {}
@expected_status = nil
end
def set(param_values)
@ -15,7 +14,7 @@ module SwaggerRails
end
def expect(status)
@expected_status = status
@expected_status = status.to_s
end
def test_data
@ -33,7 +32,7 @@ module SwaggerRails
private
def find_operation!
keys = [ 'paths', @path_template, @http_method ]
keys = [ 'paths', @path_template, @http_method ]
operation = find_hash_item!(@swagger, keys)
operation || (raise MetadataError.new(keys))
end
@ -52,35 +51,59 @@ module SwaggerRails
end
def build_params(parameters)
{}.tap do |params|
params.merge!(param_values_for(parameters, 'query'))
body_param_values = param_values_for(parameters, 'body')
params.merge!(body_param_values.values.first) if body_param_values.any?
end
body_param_values = param_values_for(parameters, 'body')
return body_param_values.values.first.to_json if body_param_values.any?
param_values_for(parameters, 'query')
end
def build_headers(parameters)
param_values_for(parameters, 'header')
.merge({
'CONTENT_TYPE' => 'application/json',
'ACCEPT' => 'application/json'
})
end
def build_expected_response(responses)
status = @expected_status || responses.keys.find { |k| k.start_with?('2') }
response = responses[status] || (raise MetadataError.new('paths', @path_template, @http_method, 'responses', status))
{
status: status.to_i,
body: response_body_for(response)
}
end
def param_values_for(parameters, location)
applicable_parameters = parameters.select { |p| p['in'] == location }
Hash[applicable_parameters.map { |p| [ p['name'], value_for(p) ] }]
Hash[applicable_parameters.map { |p| [ p['name'], param_value_for(p) ] }]
end
def value_for(param)
return @param_values[param['name']] if @param_values.has_key?(param['name'])
return param['default'] unless param['in'] == 'body'
schema_for(param['schema'])['example']
def param_value_for(parameter)
return @param_values[parameter['name']] if @param_values.has_key?(parameter['name'])
return parameter['default'] unless parameter['in'] == 'body'
schema = schema_for(parameter['schema'])
schema_example_for(schema)
end
def response_body_for(response)
return nil if response['schema'].nil?
schema = schema_for(response['schema'])
schema_example_for(schema)
end
def schema_for(schema_or_ref)
return schema_or_ref if schema_or_ref['$ref'].nil?
@swagger['definitions'][schema_or_ref['$ref'].sub('#/definitions/', '')]
end
def schema_example_for(schema)
return schema['example'] if schema['example'].present?
# If an array, try construct from the item example
if schema['type'] == 'array' && schema['item'].present?
item_schema = schema_for(schema['item'])
return [ schema_example_for(item_schema) ]
end
end
end
class MetadataError < StandardError

View File

@ -1,4 +1,4 @@
require 'swagger_rails/testing/test_data_builder'
require 'swagger_rails/testing/test_case_builder'
module SwaggerRails
@ -9,7 +9,7 @@ module SwaggerRails
end
def run_test(path_template, http_method, test, &block)
builder = TestDataBuilder.new(path_template, http_method, @swagger)
builder = TestCaseBuilder.new(path_template, http_method, @swagger)
builder.instance_exec(&block) if block_given?
test_data = builder.test_data
@ -19,7 +19,8 @@ module SwaggerRails
test_data[:headers]
)
test.assert_response(test_data[:expected_status])
test.assert_response(test_data[:expected_response][:status])
test.assert_equal(test_data[:expected_response][:body], JSON.parse(test.response.body))
end
end
end

View File

@ -1,14 +1,22 @@
class BlogsController < ApplicationController
wrap_parameters Blog
respond_to :json
# POST /blogs
def create
render json: {}
@blog = Blog.create(params[:blog])
respond_with @blog
end
# GET /blogs
def index
render json: {}
@blogs = Blog.all
respond_with @blogs
end
# GET /blogs/1
def show
render json: {}
@blog = Blog.find(params[:id])
respond_with @blog
end
end

View File

@ -0,0 +1,10 @@
class Blog < ActiveRecord::Base
attr_accessible :content, :title
def as_json(options)
{
title: title,
content: content
}
end
end

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Dummy</title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>

View File

@ -1,7 +1,7 @@
require File.expand_path('../boot', __FILE__)
# Pick the frameworks you want:
# require "active_record/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"

View File

@ -0,0 +1,25 @@
#SQLite version 3.x
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem 'sqlite3'
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 5
timeout: 5000

View File

@ -1,5 +1,5 @@
Rails.application.routes.draw do
mount SwaggerRails::Engine => '/api-docs'
resources :blogs, defaults: { :format => :json }
resources :blogs, only: [ :create, :index, :show ]
mount SwaggerRails::Engine => '/api-docs'
end

View File

@ -24,8 +24,8 @@
}
],
"responses": {
"200": {
"description": "Ok",
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/Blog"
}
@ -58,7 +58,10 @@
"200": {
"description": "Ok",
"schema": {
"$ref": "#/definitions/Blog"
"type": "array",
"item": {
"$ref": "#/definitions/Blog"
}
}
}
}

View File

@ -0,0 +1,10 @@
class CreateBlogs < ActiveRecord::Migration
def change
create_table :blogs do |t|
t.string :title
t.text :content
t.timestamps
end
end
end

23
spec/dummy/db/schema.rb Normal file
View File

@ -0,0 +1,23 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20160218212104) do
create_table "blogs", :force => true do |t|
t.string "title"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end

View File

@ -5,5 +5,14 @@ class V1ContractTest < ActionDispatch::IntegrationTest
include SwaggerRails::TestHelpers
swagger_doc 'v1/swagger.json'
swagger_test_all
#
# test '/blogs post' do
# swagger_test '/blogs', 'post'
# end
test '/blogs get' do
blog = Blog.create(title: 'Test Blog', content: 'Hello World')
swagger_test '/blogs', 'get'
end
end

View File

@ -1,9 +1,9 @@
require 'rails_helper'
require 'swagger_rails/testing/test_data_builder'
require 'swagger_rails/testing/test_case_builder'
module SwaggerRails
describe TestDataBuilder do
describe TestCaseBuilder do
subject { described_class.new(path, method, swagger) }
let(:swagger) do
file_path = File.join(Rails.root, 'config/swagger', 'v1/swagger.json')
@ -59,20 +59,20 @@ module SwaggerRails
end
end
context 'operation has body params' do
context 'operation has body param' do
let(:path) { '/blogs' }
let(:method) { 'post' }
context 'by default' do
it "includes params built from 'default' values" do
expect(test_data[:params]).to eq({ 'title' => 'Test Blog', 'content' => 'Hello World' })
it "includes params string based on schema 'example'" do
expect(test_data[:params]).to eq({ 'title' => 'Test Blog', 'content' => 'Hello World' }.to_json)
end
end
context 'values explicitly set' do
before { subject.set blog: { 'title' => 'foobar' } }
it 'includes params build from set values' do
expect(test_data[:params]).to eq({ 'title' => 'foobar' })
it 'includes params string based on set value' do
expect(test_data[:params]).to eq({ 'title' => 'foobar' }.to_json)
end
end
end
@ -83,14 +83,54 @@ module SwaggerRails
context 'by default' do
it "includes headers built from 'default' values" do
expect(test_data[:headers]).to eq({ 'X-Forwarded-For' => 'client1' })
expect(test_data[:headers]).to eq({
'X-Forwarded-For' => 'client1',
'CONTENT_TYPE' => 'application/json',
'ACCEPT' => 'application/json'
})
end
end
context 'values explicitly params' do
before { subject.set 'X-Forwarded-For' => '192.168.1.1' }
it 'includes headers built from set values' do
expect(test_data[:headers]).to eq({ 'X-Forwarded-For' => '192.168.1.1' })
expect(test_data[:headers]).to eq({
'X-Forwarded-For' => '192.168.1.1',
'CONTENT_TYPE' => 'application/json',
'ACCEPT' => 'application/json'
})
end
end
end
context 'operation returns an object' do
let(:path) { '/blogs' }
let(:method) { 'post' }
context 'by default' do
it "includes expected_response based on spec'd 2xx status" do
expect(test_data[:expected_response][:status]).to eq(201)
expect(test_data[:expected_response][:body]).to eq({ 'title' => 'Test Blog', 'content' => 'Hello World' })
end
end
context 'expected status explicitly set' do
before { subject.expect 400 }
it "includes expected_response based on set status" do
expect(test_data[:expected_response][:status]).to eq(400)
expect(test_data[:expected_response][:body]).to eq({ 'title' => [ 'is required' ] })
end
end
end
context 'operation returns an array' do
let(:path) { '/blogs' }
let(:method) { 'get' }
context 'by default' do
it "includes expected_response based on spec'd 2xx status" do
expect(test_data[:expected_response][:status]).to eq(200)
expect(test_data[:expected_response][:body]).to eq([ { 'title' => 'Test Blog', 'content' => 'Hello World' } ])
end
end
end

View File

@ -12,19 +12,30 @@ module SwaggerRails
let(:test) { spy('test') }
describe '#run_test' do
before do
allow(test).to receive(:response).and_return(OpenStruct.new(body: "{}"))
end
context 'by default' do
before { subject.run_test('/blogs', 'post', test) }
it "submits request based on 'default' and 'example' param values" do
expect(test).to have_received(:post).with(
'/blogs',
{ 'title' => 'Test Blog', 'content' => 'Hello World' },
{ 'X-Forwarded-For' => 'client1' }
{ 'title' => 'Test Blog', 'content' => 'Hello World' }.to_json,
{ 'X-Forwarded-For' => 'client1', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
)
end
it "asserts response matches first 2xx status in operation 'responses'" do
expect(test).to have_received(:assert_response).with(200)
it "asserts response matches spec'd 2xx status" do
expect(test).to have_received(:assert_response).with(201)
end
it "asserts response body matches schema 'example' for 2xx status" do
expect(test).to have_received(:assert_equal).with(
{ 'title' => 'Test Blog', 'content' => 'Hello World' },
{}
)
end
end
@ -39,22 +50,29 @@ module SwaggerRails
it 'submits a request based on provided param values' do
expect(test).to have_received(:post).with(
'/blogs',
{ 'title' => 'foobar' },
{ 'X-Forwarded-For' => '192.168.1.1' }
{ 'title' => 'foobar' }.to_json,
{ 'X-Forwarded-For' => '192.168.1.1', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
)
end
end
context 'expected status explicitly params' do
context 'expected status explicitly set' do
before do
subject.run_test('/blogs', 'post', test) do
expect 400
end
end
it "asserts response matches params status" do
it "asserts response matches set status" do
expect(test).to have_received(:assert_response).with(400)
end
it "asserts response body matches schema 'example' for set status" do
expect(test).to have_received(:assert_equal).with(
{ 'title' => [ 'is required' ] },
{}
)
end
end
end
end