Rails & RSpec - Testing API with request specs and shared examples
up vote
2
down vote
favorite
I have written the following shared examples which are used in multiple request specs to test a namespaced RESTful JSON API.
Application details: Rails 4.2, RSpec 3.5, Devise for authentication and Pundit for authorization. Authorization policies are tested separarely as discussed in this post.
I would greatly appreciate your feedback and suggestions on how to improve the specs (e.g., efficiency, readability, maintainability, DRYness).
Here are the shared examples:
RSpec.shared_examples "a RESTful JSON API",
http_error_instead_of_exception: true do |controller_class:,
resource_path:,
comparable_attributes:|
def self.controller_has_action?(controller_class, action)
controller_class.action_methods.include?(action.to_s)
end
# Ensure authorization (Pundit gem) is enforced
def mock_authorization(authorized: false)
# Avoid Pundit::AuthorizationNotPerformedError when using "after_action
# :verify_authorized". Use "allow" and not "expect" as #verify_authorized is
# only called when we do not raise Pundit::NotAuthorizedError.
allow_any_instance_of(Api::V1::BaseApiController).to
receive(:verify_authorized)
expectation = expect_any_instance_of(Api::V1::BaseApiController).to
receive(:authorize)
# Simulate a "not authorized" scenario
expectation.and_raise(Pundit::NotAuthorizedError) if !authorized
end
resource_singular = resource_path.split("/").last.singularize.to_sym
resource_plural = resource_path.split("/").last.to_sym
before(:each) { login_admin }
let(:record) { FactoryGirl.create(resource_singular) }
let(:records) { FactoryGirl.create_pair(resource_singular) }
# Models that validate the presence of associated records require some
# hacking in the factory to include associations in #attributes_for
let(:valid_attributes) { FactoryGirl.attributes_for(resource_singular) }
# All factories must have a trait called :invalid
let(:invalid_attributes) do
FactoryGirl.attributes_for(resource_singular, :invalid)
end
let(:response_json) { JSON.parse(response.body) }
describe "GET #{resource_path} (#index)",
if: controller_has_action?(controller_class, :index) do
before(:each) do
# Test data is lazily created. Here we must force it to be created.
records
end
it "requires authentication" do
logout_example
get resource_path
expect(response).to require_login_api
end
it "enforces authorization" do
expect_any_instance_of(Api::V1::BaseApiController).to
receive(:policy_scope).and_call_original
get resource_path
end
it "returns a 'OK' (200) HTTP status code" do
get resource_path
expect(response).to have_http_status(200)
end
it "returns all #{resource_plural}" do
get resource_path
# When testing the User model, a user created by the Devise login helper
# increases the expected record count to 3.
expected_count = resource_singular == :user ? 3 : 2
expect(response_json.size).to eq(expected_count)
end
end
describe "GET #{resource_path}/:id (#show)",
if: controller_has_action?(controller_class, :show) do
it "requires authentication" do
logout_example
get "#{resource_path}/#{record.id}"
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
get "#{resource_path}/#{record.id}"
expect(response).to have_http_status(403)
end
context "with a valid #{resource_singular} ID" do
before(:each) do
get "#{resource_path}/#{record.id}"
end
it "returns a 'OK' (200) HTTP status code" do
expect(response).to have_http_status(200)
end
it "returns the requested #{resource_singular}" do
expect(response_json).to include(
record.attributes.slice(comparable_attributes))
end
end
context "with an invalid #{resource_singular} ID" do
before(:each) { get "#{resource_path}/9999" }
it "returns a 'not found' (404) status code" do
expect(response).to have_http_status(404)
end
end
end
describe "POST #{resource_path} (#create)",
if: controller_has_action?(controller_class, :create) do
it "requires authentication" do
logout_example
post resource_path, { resource_singular => valid_attributes }
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
post resource_path, { resource_singular => valid_attributes }
expect(response).to have_http_status(403)
end
context "with valid attributes" do
before(:each) do
post resource_path, { resource_singular => valid_attributes }
end
it "returns a 'created' (201) HTTP status code" do
expect(response).to have_http_status(201)
end
it "returns the created #{resource_singular}" do
expect(response_json).to include(
record.attributes.slice(comparable_attributes))
end
end
context "with invalid attributes" do
before(:each) do
post resource_path, { resource_singular => invalid_attributes }
end
it "returns a 'unprocessable entity' (422) HTTP status code" do
expect(response).to have_http_status(422)
end
end
end
describe "PATCH #{resource_path}/:id (#update)",
if: controller_has_action?(controller_class, :update) do
it "requires authentication" do
logout_example
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
expect(response).to have_http_status(403)
end
context "with valid attributes" do
before(:each) do
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
end
it "returns a 'OK' (200) HTTP status code" do
expect(response).to have_http_status(200)
end
it "returns the updated #{resource_singular}" do
record.reload
expect(response_json).to include(
valid_attributes.slice(comparable_attributes))
end
end
context "with invalid attributes" do
before(:each) do
patch "#{resource_path}/#{record.id}",
{ resource_singular => invalid_attributes }
end
it "returns an 'unprocessable entity' (422) status code" do
expect(response).to have_http_status(422)
end
end
end
describe "DELETE #{resource_path}/:id (#destroy)",
if: controller_has_action?(controller_class, :destroy) do
it "requires authentication" do
logout_example
delete "#{resource_path}/#{record.id}"
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
delete "#{resource_path}/#{record.id}"
expect(response).to have_http_status(403)
end
it "ensures the #{resource_singular} no longer exists" do
delete "#{resource_path}/#{record.id}"
# When testing the "user" resource, Devise unexpectedly logs out
# (resulting in 401 to any further requests) after *any* user is deleted.
login_admin if resource_singular == :user
get "#{resource_path}/#{record.id}"
expect(response).to have_http_status(404)
end
it "returns a 'no content' (204) status code" do
delete "#{resource_path}/#{record.id}"
expect(response).to have_http_status(204)
end
end
end
... and this is a sample request spec:
# spec/requests/users_spec.rb
require "rails_helper"
RSpec.describe "Users API", :type => :request do
it_behaves_like "a RESTful JSON API",
controller_class: Api::V1::UsersController,
resource_path: "/api/v1/users",
comparable_attributes: [:id, :email, :first_name, :last_name]
end
Thanks in advance.
ruby unit-testing ruby-on-rails api rspec
bumped to the homepage by Community♦ 10 hours ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
up vote
2
down vote
favorite
I have written the following shared examples which are used in multiple request specs to test a namespaced RESTful JSON API.
Application details: Rails 4.2, RSpec 3.5, Devise for authentication and Pundit for authorization. Authorization policies are tested separarely as discussed in this post.
I would greatly appreciate your feedback and suggestions on how to improve the specs (e.g., efficiency, readability, maintainability, DRYness).
Here are the shared examples:
RSpec.shared_examples "a RESTful JSON API",
http_error_instead_of_exception: true do |controller_class:,
resource_path:,
comparable_attributes:|
def self.controller_has_action?(controller_class, action)
controller_class.action_methods.include?(action.to_s)
end
# Ensure authorization (Pundit gem) is enforced
def mock_authorization(authorized: false)
# Avoid Pundit::AuthorizationNotPerformedError when using "after_action
# :verify_authorized". Use "allow" and not "expect" as #verify_authorized is
# only called when we do not raise Pundit::NotAuthorizedError.
allow_any_instance_of(Api::V1::BaseApiController).to
receive(:verify_authorized)
expectation = expect_any_instance_of(Api::V1::BaseApiController).to
receive(:authorize)
# Simulate a "not authorized" scenario
expectation.and_raise(Pundit::NotAuthorizedError) if !authorized
end
resource_singular = resource_path.split("/").last.singularize.to_sym
resource_plural = resource_path.split("/").last.to_sym
before(:each) { login_admin }
let(:record) { FactoryGirl.create(resource_singular) }
let(:records) { FactoryGirl.create_pair(resource_singular) }
# Models that validate the presence of associated records require some
# hacking in the factory to include associations in #attributes_for
let(:valid_attributes) { FactoryGirl.attributes_for(resource_singular) }
# All factories must have a trait called :invalid
let(:invalid_attributes) do
FactoryGirl.attributes_for(resource_singular, :invalid)
end
let(:response_json) { JSON.parse(response.body) }
describe "GET #{resource_path} (#index)",
if: controller_has_action?(controller_class, :index) do
before(:each) do
# Test data is lazily created. Here we must force it to be created.
records
end
it "requires authentication" do
logout_example
get resource_path
expect(response).to require_login_api
end
it "enforces authorization" do
expect_any_instance_of(Api::V1::BaseApiController).to
receive(:policy_scope).and_call_original
get resource_path
end
it "returns a 'OK' (200) HTTP status code" do
get resource_path
expect(response).to have_http_status(200)
end
it "returns all #{resource_plural}" do
get resource_path
# When testing the User model, a user created by the Devise login helper
# increases the expected record count to 3.
expected_count = resource_singular == :user ? 3 : 2
expect(response_json.size).to eq(expected_count)
end
end
describe "GET #{resource_path}/:id (#show)",
if: controller_has_action?(controller_class, :show) do
it "requires authentication" do
logout_example
get "#{resource_path}/#{record.id}"
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
get "#{resource_path}/#{record.id}"
expect(response).to have_http_status(403)
end
context "with a valid #{resource_singular} ID" do
before(:each) do
get "#{resource_path}/#{record.id}"
end
it "returns a 'OK' (200) HTTP status code" do
expect(response).to have_http_status(200)
end
it "returns the requested #{resource_singular}" do
expect(response_json).to include(
record.attributes.slice(comparable_attributes))
end
end
context "with an invalid #{resource_singular} ID" do
before(:each) { get "#{resource_path}/9999" }
it "returns a 'not found' (404) status code" do
expect(response).to have_http_status(404)
end
end
end
describe "POST #{resource_path} (#create)",
if: controller_has_action?(controller_class, :create) do
it "requires authentication" do
logout_example
post resource_path, { resource_singular => valid_attributes }
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
post resource_path, { resource_singular => valid_attributes }
expect(response).to have_http_status(403)
end
context "with valid attributes" do
before(:each) do
post resource_path, { resource_singular => valid_attributes }
end
it "returns a 'created' (201) HTTP status code" do
expect(response).to have_http_status(201)
end
it "returns the created #{resource_singular}" do
expect(response_json).to include(
record.attributes.slice(comparable_attributes))
end
end
context "with invalid attributes" do
before(:each) do
post resource_path, { resource_singular => invalid_attributes }
end
it "returns a 'unprocessable entity' (422) HTTP status code" do
expect(response).to have_http_status(422)
end
end
end
describe "PATCH #{resource_path}/:id (#update)",
if: controller_has_action?(controller_class, :update) do
it "requires authentication" do
logout_example
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
expect(response).to have_http_status(403)
end
context "with valid attributes" do
before(:each) do
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
end
it "returns a 'OK' (200) HTTP status code" do
expect(response).to have_http_status(200)
end
it "returns the updated #{resource_singular}" do
record.reload
expect(response_json).to include(
valid_attributes.slice(comparable_attributes))
end
end
context "with invalid attributes" do
before(:each) do
patch "#{resource_path}/#{record.id}",
{ resource_singular => invalid_attributes }
end
it "returns an 'unprocessable entity' (422) status code" do
expect(response).to have_http_status(422)
end
end
end
describe "DELETE #{resource_path}/:id (#destroy)",
if: controller_has_action?(controller_class, :destroy) do
it "requires authentication" do
logout_example
delete "#{resource_path}/#{record.id}"
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
delete "#{resource_path}/#{record.id}"
expect(response).to have_http_status(403)
end
it "ensures the #{resource_singular} no longer exists" do
delete "#{resource_path}/#{record.id}"
# When testing the "user" resource, Devise unexpectedly logs out
# (resulting in 401 to any further requests) after *any* user is deleted.
login_admin if resource_singular == :user
get "#{resource_path}/#{record.id}"
expect(response).to have_http_status(404)
end
it "returns a 'no content' (204) status code" do
delete "#{resource_path}/#{record.id}"
expect(response).to have_http_status(204)
end
end
end
... and this is a sample request spec:
# spec/requests/users_spec.rb
require "rails_helper"
RSpec.describe "Users API", :type => :request do
it_behaves_like "a RESTful JSON API",
controller_class: Api::V1::UsersController,
resource_path: "/api/v1/users",
comparable_attributes: [:id, :email, :first_name, :last_name]
end
Thanks in advance.
ruby unit-testing ruby-on-rails api rspec
bumped to the homepage by Community♦ 10 hours ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
I have written the following shared examples which are used in multiple request specs to test a namespaced RESTful JSON API.
Application details: Rails 4.2, RSpec 3.5, Devise for authentication and Pundit for authorization. Authorization policies are tested separarely as discussed in this post.
I would greatly appreciate your feedback and suggestions on how to improve the specs (e.g., efficiency, readability, maintainability, DRYness).
Here are the shared examples:
RSpec.shared_examples "a RESTful JSON API",
http_error_instead_of_exception: true do |controller_class:,
resource_path:,
comparable_attributes:|
def self.controller_has_action?(controller_class, action)
controller_class.action_methods.include?(action.to_s)
end
# Ensure authorization (Pundit gem) is enforced
def mock_authorization(authorized: false)
# Avoid Pundit::AuthorizationNotPerformedError when using "after_action
# :verify_authorized". Use "allow" and not "expect" as #verify_authorized is
# only called when we do not raise Pundit::NotAuthorizedError.
allow_any_instance_of(Api::V1::BaseApiController).to
receive(:verify_authorized)
expectation = expect_any_instance_of(Api::V1::BaseApiController).to
receive(:authorize)
# Simulate a "not authorized" scenario
expectation.and_raise(Pundit::NotAuthorizedError) if !authorized
end
resource_singular = resource_path.split("/").last.singularize.to_sym
resource_plural = resource_path.split("/").last.to_sym
before(:each) { login_admin }
let(:record) { FactoryGirl.create(resource_singular) }
let(:records) { FactoryGirl.create_pair(resource_singular) }
# Models that validate the presence of associated records require some
# hacking in the factory to include associations in #attributes_for
let(:valid_attributes) { FactoryGirl.attributes_for(resource_singular) }
# All factories must have a trait called :invalid
let(:invalid_attributes) do
FactoryGirl.attributes_for(resource_singular, :invalid)
end
let(:response_json) { JSON.parse(response.body) }
describe "GET #{resource_path} (#index)",
if: controller_has_action?(controller_class, :index) do
before(:each) do
# Test data is lazily created. Here we must force it to be created.
records
end
it "requires authentication" do
logout_example
get resource_path
expect(response).to require_login_api
end
it "enforces authorization" do
expect_any_instance_of(Api::V1::BaseApiController).to
receive(:policy_scope).and_call_original
get resource_path
end
it "returns a 'OK' (200) HTTP status code" do
get resource_path
expect(response).to have_http_status(200)
end
it "returns all #{resource_plural}" do
get resource_path
# When testing the User model, a user created by the Devise login helper
# increases the expected record count to 3.
expected_count = resource_singular == :user ? 3 : 2
expect(response_json.size).to eq(expected_count)
end
end
describe "GET #{resource_path}/:id (#show)",
if: controller_has_action?(controller_class, :show) do
it "requires authentication" do
logout_example
get "#{resource_path}/#{record.id}"
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
get "#{resource_path}/#{record.id}"
expect(response).to have_http_status(403)
end
context "with a valid #{resource_singular} ID" do
before(:each) do
get "#{resource_path}/#{record.id}"
end
it "returns a 'OK' (200) HTTP status code" do
expect(response).to have_http_status(200)
end
it "returns the requested #{resource_singular}" do
expect(response_json).to include(
record.attributes.slice(comparable_attributes))
end
end
context "with an invalid #{resource_singular} ID" do
before(:each) { get "#{resource_path}/9999" }
it "returns a 'not found' (404) status code" do
expect(response).to have_http_status(404)
end
end
end
describe "POST #{resource_path} (#create)",
if: controller_has_action?(controller_class, :create) do
it "requires authentication" do
logout_example
post resource_path, { resource_singular => valid_attributes }
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
post resource_path, { resource_singular => valid_attributes }
expect(response).to have_http_status(403)
end
context "with valid attributes" do
before(:each) do
post resource_path, { resource_singular => valid_attributes }
end
it "returns a 'created' (201) HTTP status code" do
expect(response).to have_http_status(201)
end
it "returns the created #{resource_singular}" do
expect(response_json).to include(
record.attributes.slice(comparable_attributes))
end
end
context "with invalid attributes" do
before(:each) do
post resource_path, { resource_singular => invalid_attributes }
end
it "returns a 'unprocessable entity' (422) HTTP status code" do
expect(response).to have_http_status(422)
end
end
end
describe "PATCH #{resource_path}/:id (#update)",
if: controller_has_action?(controller_class, :update) do
it "requires authentication" do
logout_example
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
expect(response).to have_http_status(403)
end
context "with valid attributes" do
before(:each) do
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
end
it "returns a 'OK' (200) HTTP status code" do
expect(response).to have_http_status(200)
end
it "returns the updated #{resource_singular}" do
record.reload
expect(response_json).to include(
valid_attributes.slice(comparable_attributes))
end
end
context "with invalid attributes" do
before(:each) do
patch "#{resource_path}/#{record.id}",
{ resource_singular => invalid_attributes }
end
it "returns an 'unprocessable entity' (422) status code" do
expect(response).to have_http_status(422)
end
end
end
describe "DELETE #{resource_path}/:id (#destroy)",
if: controller_has_action?(controller_class, :destroy) do
it "requires authentication" do
logout_example
delete "#{resource_path}/#{record.id}"
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
delete "#{resource_path}/#{record.id}"
expect(response).to have_http_status(403)
end
it "ensures the #{resource_singular} no longer exists" do
delete "#{resource_path}/#{record.id}"
# When testing the "user" resource, Devise unexpectedly logs out
# (resulting in 401 to any further requests) after *any* user is deleted.
login_admin if resource_singular == :user
get "#{resource_path}/#{record.id}"
expect(response).to have_http_status(404)
end
it "returns a 'no content' (204) status code" do
delete "#{resource_path}/#{record.id}"
expect(response).to have_http_status(204)
end
end
end
... and this is a sample request spec:
# spec/requests/users_spec.rb
require "rails_helper"
RSpec.describe "Users API", :type => :request do
it_behaves_like "a RESTful JSON API",
controller_class: Api::V1::UsersController,
resource_path: "/api/v1/users",
comparable_attributes: [:id, :email, :first_name, :last_name]
end
Thanks in advance.
ruby unit-testing ruby-on-rails api rspec
I have written the following shared examples which are used in multiple request specs to test a namespaced RESTful JSON API.
Application details: Rails 4.2, RSpec 3.5, Devise for authentication and Pundit for authorization. Authorization policies are tested separarely as discussed in this post.
I would greatly appreciate your feedback and suggestions on how to improve the specs (e.g., efficiency, readability, maintainability, DRYness).
Here are the shared examples:
RSpec.shared_examples "a RESTful JSON API",
http_error_instead_of_exception: true do |controller_class:,
resource_path:,
comparable_attributes:|
def self.controller_has_action?(controller_class, action)
controller_class.action_methods.include?(action.to_s)
end
# Ensure authorization (Pundit gem) is enforced
def mock_authorization(authorized: false)
# Avoid Pundit::AuthorizationNotPerformedError when using "after_action
# :verify_authorized". Use "allow" and not "expect" as #verify_authorized is
# only called when we do not raise Pundit::NotAuthorizedError.
allow_any_instance_of(Api::V1::BaseApiController).to
receive(:verify_authorized)
expectation = expect_any_instance_of(Api::V1::BaseApiController).to
receive(:authorize)
# Simulate a "not authorized" scenario
expectation.and_raise(Pundit::NotAuthorizedError) if !authorized
end
resource_singular = resource_path.split("/").last.singularize.to_sym
resource_plural = resource_path.split("/").last.to_sym
before(:each) { login_admin }
let(:record) { FactoryGirl.create(resource_singular) }
let(:records) { FactoryGirl.create_pair(resource_singular) }
# Models that validate the presence of associated records require some
# hacking in the factory to include associations in #attributes_for
let(:valid_attributes) { FactoryGirl.attributes_for(resource_singular) }
# All factories must have a trait called :invalid
let(:invalid_attributes) do
FactoryGirl.attributes_for(resource_singular, :invalid)
end
let(:response_json) { JSON.parse(response.body) }
describe "GET #{resource_path} (#index)",
if: controller_has_action?(controller_class, :index) do
before(:each) do
# Test data is lazily created. Here we must force it to be created.
records
end
it "requires authentication" do
logout_example
get resource_path
expect(response).to require_login_api
end
it "enforces authorization" do
expect_any_instance_of(Api::V1::BaseApiController).to
receive(:policy_scope).and_call_original
get resource_path
end
it "returns a 'OK' (200) HTTP status code" do
get resource_path
expect(response).to have_http_status(200)
end
it "returns all #{resource_plural}" do
get resource_path
# When testing the User model, a user created by the Devise login helper
# increases the expected record count to 3.
expected_count = resource_singular == :user ? 3 : 2
expect(response_json.size).to eq(expected_count)
end
end
describe "GET #{resource_path}/:id (#show)",
if: controller_has_action?(controller_class, :show) do
it "requires authentication" do
logout_example
get "#{resource_path}/#{record.id}"
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
get "#{resource_path}/#{record.id}"
expect(response).to have_http_status(403)
end
context "with a valid #{resource_singular} ID" do
before(:each) do
get "#{resource_path}/#{record.id}"
end
it "returns a 'OK' (200) HTTP status code" do
expect(response).to have_http_status(200)
end
it "returns the requested #{resource_singular}" do
expect(response_json).to include(
record.attributes.slice(comparable_attributes))
end
end
context "with an invalid #{resource_singular} ID" do
before(:each) { get "#{resource_path}/9999" }
it "returns a 'not found' (404) status code" do
expect(response).to have_http_status(404)
end
end
end
describe "POST #{resource_path} (#create)",
if: controller_has_action?(controller_class, :create) do
it "requires authentication" do
logout_example
post resource_path, { resource_singular => valid_attributes }
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
post resource_path, { resource_singular => valid_attributes }
expect(response).to have_http_status(403)
end
context "with valid attributes" do
before(:each) do
post resource_path, { resource_singular => valid_attributes }
end
it "returns a 'created' (201) HTTP status code" do
expect(response).to have_http_status(201)
end
it "returns the created #{resource_singular}" do
expect(response_json).to include(
record.attributes.slice(comparable_attributes))
end
end
context "with invalid attributes" do
before(:each) do
post resource_path, { resource_singular => invalid_attributes }
end
it "returns a 'unprocessable entity' (422) HTTP status code" do
expect(response).to have_http_status(422)
end
end
end
describe "PATCH #{resource_path}/:id (#update)",
if: controller_has_action?(controller_class, :update) do
it "requires authentication" do
logout_example
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
expect(response).to have_http_status(403)
end
context "with valid attributes" do
before(:each) do
patch "#{resource_path}/#{record.id}",
{ resource_singular => valid_attributes }
end
it "returns a 'OK' (200) HTTP status code" do
expect(response).to have_http_status(200)
end
it "returns the updated #{resource_singular}" do
record.reload
expect(response_json).to include(
valid_attributes.slice(comparable_attributes))
end
end
context "with invalid attributes" do
before(:each) do
patch "#{resource_path}/#{record.id}",
{ resource_singular => invalid_attributes }
end
it "returns an 'unprocessable entity' (422) status code" do
expect(response).to have_http_status(422)
end
end
end
describe "DELETE #{resource_path}/:id (#destroy)",
if: controller_has_action?(controller_class, :destroy) do
it "requires authentication" do
logout_example
delete "#{resource_path}/#{record.id}"
expect(response).to require_login_api
end
it "enforces authorization" do
mock_authorization(authorized: false)
delete "#{resource_path}/#{record.id}"
expect(response).to have_http_status(403)
end
it "ensures the #{resource_singular} no longer exists" do
delete "#{resource_path}/#{record.id}"
# When testing the "user" resource, Devise unexpectedly logs out
# (resulting in 401 to any further requests) after *any* user is deleted.
login_admin if resource_singular == :user
get "#{resource_path}/#{record.id}"
expect(response).to have_http_status(404)
end
it "returns a 'no content' (204) status code" do
delete "#{resource_path}/#{record.id}"
expect(response).to have_http_status(204)
end
end
end
... and this is a sample request spec:
# spec/requests/users_spec.rb
require "rails_helper"
RSpec.describe "Users API", :type => :request do
it_behaves_like "a RESTful JSON API",
controller_class: Api::V1::UsersController,
resource_path: "/api/v1/users",
comparable_attributes: [:id, :email, :first_name, :last_name]
end
Thanks in advance.
ruby unit-testing ruby-on-rails api rspec
ruby unit-testing ruby-on-rails api rspec
edited Jul 13 '17 at 21:45
asked Jul 10 '17 at 21:25
BrunoFacca
658
658
bumped to the homepage by Community♦ 10 hours ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
bumped to the homepage by Community♦ 10 hours ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
up vote
0
down vote
A suggestion would like to give, not sure how useful it would turn out for you.
Separate out the controller level validations from Model level validations.
For validating the data for the users whether they have mentioned attributes or not, "comparable_attributes: [:id, :email, :first_name, :last_name]", mention such validation at Model level spec file.
For instance:
require 'rails_helper'
describe User do
#Tests for fields
it {is_expected.to respond_to :firstname}
it {is_expected.to respond_to :lastname}
it {is_expected.to respond_to :email}
#Tests for validations
describe 'validate lengths' do
it {is_expected.to validate_length_of(:email).is_at_most(255)}
it {is_expected.to validate_length_of(:firstname).is_at_most(255)}
it {is_expected.to validate_length_of(:lastname).is_at_most(255)}
end
describe 'validate presence' do
it {is_expected.to validate_presence_of :firstname }
it {is_expected.to validate_presence_of :lastname }
end
end
Create a user in the model with valid parameters and set it in the current_user and that same object can be used in the controller, where you can authenticate various controller request
Thanks for your reply. Besides request specs, the application also has model specs which test validations and other model behaviors. Thecomparable_attributesin the API tests serve to ensure that records which are retrieved, created or updated via API have the expected attribute values. Hence, it is not the same as testing the model. Regarding the creation of test data (e.g., a valid user object), I'm using Factory Girl.
– BrunoFacca
Jul 19 '17 at 12:26
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
A suggestion would like to give, not sure how useful it would turn out for you.
Separate out the controller level validations from Model level validations.
For validating the data for the users whether they have mentioned attributes or not, "comparable_attributes: [:id, :email, :first_name, :last_name]", mention such validation at Model level spec file.
For instance:
require 'rails_helper'
describe User do
#Tests for fields
it {is_expected.to respond_to :firstname}
it {is_expected.to respond_to :lastname}
it {is_expected.to respond_to :email}
#Tests for validations
describe 'validate lengths' do
it {is_expected.to validate_length_of(:email).is_at_most(255)}
it {is_expected.to validate_length_of(:firstname).is_at_most(255)}
it {is_expected.to validate_length_of(:lastname).is_at_most(255)}
end
describe 'validate presence' do
it {is_expected.to validate_presence_of :firstname }
it {is_expected.to validate_presence_of :lastname }
end
end
Create a user in the model with valid parameters and set it in the current_user and that same object can be used in the controller, where you can authenticate various controller request
Thanks for your reply. Besides request specs, the application also has model specs which test validations and other model behaviors. Thecomparable_attributesin the API tests serve to ensure that records which are retrieved, created or updated via API have the expected attribute values. Hence, it is not the same as testing the model. Regarding the creation of test data (e.g., a valid user object), I'm using Factory Girl.
– BrunoFacca
Jul 19 '17 at 12:26
add a comment |
up vote
0
down vote
A suggestion would like to give, not sure how useful it would turn out for you.
Separate out the controller level validations from Model level validations.
For validating the data for the users whether they have mentioned attributes or not, "comparable_attributes: [:id, :email, :first_name, :last_name]", mention such validation at Model level spec file.
For instance:
require 'rails_helper'
describe User do
#Tests for fields
it {is_expected.to respond_to :firstname}
it {is_expected.to respond_to :lastname}
it {is_expected.to respond_to :email}
#Tests for validations
describe 'validate lengths' do
it {is_expected.to validate_length_of(:email).is_at_most(255)}
it {is_expected.to validate_length_of(:firstname).is_at_most(255)}
it {is_expected.to validate_length_of(:lastname).is_at_most(255)}
end
describe 'validate presence' do
it {is_expected.to validate_presence_of :firstname }
it {is_expected.to validate_presence_of :lastname }
end
end
Create a user in the model with valid parameters and set it in the current_user and that same object can be used in the controller, where you can authenticate various controller request
Thanks for your reply. Besides request specs, the application also has model specs which test validations and other model behaviors. Thecomparable_attributesin the API tests serve to ensure that records which are retrieved, created or updated via API have the expected attribute values. Hence, it is not the same as testing the model. Regarding the creation of test data (e.g., a valid user object), I'm using Factory Girl.
– BrunoFacca
Jul 19 '17 at 12:26
add a comment |
up vote
0
down vote
up vote
0
down vote
A suggestion would like to give, not sure how useful it would turn out for you.
Separate out the controller level validations from Model level validations.
For validating the data for the users whether they have mentioned attributes or not, "comparable_attributes: [:id, :email, :first_name, :last_name]", mention such validation at Model level spec file.
For instance:
require 'rails_helper'
describe User do
#Tests for fields
it {is_expected.to respond_to :firstname}
it {is_expected.to respond_to :lastname}
it {is_expected.to respond_to :email}
#Tests for validations
describe 'validate lengths' do
it {is_expected.to validate_length_of(:email).is_at_most(255)}
it {is_expected.to validate_length_of(:firstname).is_at_most(255)}
it {is_expected.to validate_length_of(:lastname).is_at_most(255)}
end
describe 'validate presence' do
it {is_expected.to validate_presence_of :firstname }
it {is_expected.to validate_presence_of :lastname }
end
end
Create a user in the model with valid parameters and set it in the current_user and that same object can be used in the controller, where you can authenticate various controller request
A suggestion would like to give, not sure how useful it would turn out for you.
Separate out the controller level validations from Model level validations.
For validating the data for the users whether they have mentioned attributes or not, "comparable_attributes: [:id, :email, :first_name, :last_name]", mention such validation at Model level spec file.
For instance:
require 'rails_helper'
describe User do
#Tests for fields
it {is_expected.to respond_to :firstname}
it {is_expected.to respond_to :lastname}
it {is_expected.to respond_to :email}
#Tests for validations
describe 'validate lengths' do
it {is_expected.to validate_length_of(:email).is_at_most(255)}
it {is_expected.to validate_length_of(:firstname).is_at_most(255)}
it {is_expected.to validate_length_of(:lastname).is_at_most(255)}
end
describe 'validate presence' do
it {is_expected.to validate_presence_of :firstname }
it {is_expected.to validate_presence_of :lastname }
end
end
Create a user in the model with valid parameters and set it in the current_user and that same object can be used in the controller, where you can authenticate various controller request
answered Jul 19 '17 at 3:39
Neha Chopra
1313
1313
Thanks for your reply. Besides request specs, the application also has model specs which test validations and other model behaviors. Thecomparable_attributesin the API tests serve to ensure that records which are retrieved, created or updated via API have the expected attribute values. Hence, it is not the same as testing the model. Regarding the creation of test data (e.g., a valid user object), I'm using Factory Girl.
– BrunoFacca
Jul 19 '17 at 12:26
add a comment |
Thanks for your reply. Besides request specs, the application also has model specs which test validations and other model behaviors. Thecomparable_attributesin the API tests serve to ensure that records which are retrieved, created or updated via API have the expected attribute values. Hence, it is not the same as testing the model. Regarding the creation of test data (e.g., a valid user object), I'm using Factory Girl.
– BrunoFacca
Jul 19 '17 at 12:26
Thanks for your reply. Besides request specs, the application also has model specs which test validations and other model behaviors. The
comparable_attributes in the API tests serve to ensure that records which are retrieved, created or updated via API have the expected attribute values. Hence, it is not the same as testing the model. Regarding the creation of test data (e.g., a valid user object), I'm using Factory Girl.– BrunoFacca
Jul 19 '17 at 12:26
Thanks for your reply. Besides request specs, the application also has model specs which test validations and other model behaviors. The
comparable_attributes in the API tests serve to ensure that records which are retrieved, created or updated via API have the expected attribute values. Hence, it is not the same as testing the model. Regarding the creation of test data (e.g., a valid user object), I'm using Factory Girl.– BrunoFacca
Jul 19 '17 at 12:26
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f168903%2frails-rspec-testing-api-with-request-specs-and-shared-examples%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown