How to test that an API response has keys in RSpec - ruby-on-rails-4

I have an endpoint that responds with an object's attributes + attributes that are added from an ActiveModelSerializer. I want to write a test that checks to see if the response has keys.
Let's hypothetically say that the object (say a tree) has these keys
expected_tree_attributes = [:height, :age, :color]
How do I write this test properly? Can I write:
subject { post :obtain_tree_info, { id: tree.id } }
response = JSON.parse(subject.body)
expected(response).to include(*expected_tree_attributes)
IS that... acceptable?

Please consider to use rspec-api-matchers gem
or airborne gem
With these you can do:
# api_matchers
response = JSON.parse(subject.body)
expect(response).to be_success
expect(response).to have_json_node(:height).with(tree.height)
expect(response).to have_json_node(:age).with(tree.age)
expect(response).to have_json_node(:color).with(tree.color)
# or
expect(response).to have_json_node(:age).with("123")
Airborne
describe 'sample spec' do
it 'should validate types' do
post '/api/v1/obtain_tree_info', {id: tree.id}
expect_json_types(height: :int, age: :int_or_null, color: :string)
end
end

Related

Shoulda matchers - Rspec - Test controllers Actions to Json Response - FastJSON

How to test using shoulda-matchers for these actions, to response JSON success 200, and fail 422
and how to test with normal RSpec: both cases if you can:
I think there is missing some information for shoulda matches on its repository.
if you can help thanks very much.
module V1
class MeasurementsController < ApplicationController
protect_from_forgery with: :null_session
before_action :set_measurement, only: %i[destroy]
def index
measurement = Measurement.all
render json: MeasurementSerializer.new(measurement).serialized_json
end
def create
measurement = Measurement.new(measurement_params)
if measurement.save
render json: MeasurementSerializer.new(measurement).serialized_json
else
render json: { error: measurement.errors.messages }, status: 422
end
end
def destroy
if #measurement.destroy
head :no_content
else
render json: { error: measurement.errors.messages }, status: 422
end
end
private
def set_measurement
#measurement = Measurement.find(params[:id])
end
def measurement_params
params.require(:measurement).permit(:time, :date, :sport_id)
end
end
end
end```
No need for shoulda-matchers
You can test with something like this:
require 'rails_helper'
describe V1::MeasurementsController do
describe '#index', type: request do
it 'respond with 200' do
get /your/endpoint
expect(response).to have_http_status(200)
end
end
end
what works for me
describe 'GET #index' do
before { get :index }
it { should respond_with(200) }
end

Authenticated Route not working for Rspec test

I'm following this post about setting up authentication in the routes of my Rails 4 application.
Here is my routes.rb file:
Rails.application.routes.draw do
devise_for :employees, :controllers => { registrations: 'employees/registrations' }
devise_for :clients
authenticate :employee do
resources :quotation_requests, only: [:show, :edit,:index, :update, :destroy]
end
resources :quotation_requests, only: [:new, :create]
get '/dashboard' => 'dashboard#show', as: 'show_dashboard'
root to: 'home#index'
end
Here is my quotation_requests_controller_spec.rb file:
require 'rails_helper'
RSpec.describe QuotationRequestsController, type: :controller do
describe "GET index" do
it "renders :index template" do
get :index
expect(response).to render_template(:index)
end
it "assigns quotation requests to template" do
quotation_requests = FactoryGirl.create_list(:quotation_request, 3)
get :index
expect(assigns(:quotation_requests)).to match_array(quotation_requests)
end
end
describe "GET edit" do
let(:quotation_request) { FactoryGirl.create(:quotation_request)}
it "renders :edit template" do
get :edit, id: quotation_request
expect(response).to render_template(:edit)
end
it "assigns the requested quotation request to template" do
get :edit, id: quotation_request
expect(assigns(:quotation_request)).to eq(quotation_request)
end
end
describe "PUT update" do
let(:quotation_request) { FactoryGirl.create(:quotation_request)}
context "valid data" do
new_text = Faker::Lorem.sentence(word_count=500)
let(:valid_data) { FactoryGirl.attributes_for(:quotation_request, sample_text: new_text)}
it "redirects to quotation_request#showtemplate" do
put :update, id: quotation_request, quotation_request: valid_data
expect(response).to redirect_to(quotation_request)
end
it "updates quotation request in the database" do
put :update, id: quotation_request, quotation_request: valid_data
quotation_request.reload #need to reload the object because we have just updated it in the database so need to get the new values
expect(quotation_request.sample_text).to eq(new_text)
end
end
context "invalid data" do
let(:invalid_data) { FactoryGirl.attributes_for(:quotation_request, sample_text: "", number_of_words: 400)}
it "renders the :edit template" do
put :update, id: quotation_request, quotation_request: invalid_data
expect(response).to render_template(:edit)
end
it "does not update the quotation_request in the database" do
put :update, id: quotation_request, quotation_request: invalid_data
quotation_request.reload
expect(quotation_request.number_of_words).not_to eq(400)
end
end
end
describe "GET new", new: true do
it "renders :new template" do
get :new
expect(response).to render_template(:new)
end
it "assigns new QuotationRequest to #quotation_request" do
get :new
expect(assigns(:quotation_request)).to be_a_new(QuotationRequest)
end
end
describe "GET show" do
#this test requires that there be a quotation request in the database
let(:quotation_request) { FactoryGirl.create(:quotation_request) }
context 'invalid request' do
it "does not render :show template if an employee or client is not signed in" do
#setup
quotation_request = create(:quotation_request)
#exercise
get :show, id: quotation_request
#verification
expect(response).to_not render_template(:show)
end
end
context 'valid request' do
sign_in_proofreader
it "renders :show template if an employee or client is signed in" do
#setup
quotation_request = create(:quotation_request)
#exercise
get :show, id: quotation_request
#verification
expect(response).to render_template(:show)
end
it "assigns requested quotation_request to #quotation_request" do
get :show, id: quotation_request
expect(assigns(:quotation_request)).to eq(quotation_request)
end
end
end
describe "POST create", post: true do
context "valid data" do
let(:valid_data) {FactoryGirl.nested_attributes_for(:quotation_request)}
it "redirects to quotation_requests#show" do
post :create, quotation_request: valid_data
expect(response).to redirect_to(quotation_request_path(assigns[:quotation_request]))
end
it "creates new quotation_request in database" do
expect {
post :create, quotation_request: valid_data
}.to change(QuotationRequest, :count).by(1)
end
end
context "invalid data" do
let(:invalid_data) {FactoryGirl.nested_attributes_for(:quotation_request).merge(sample_text: 'not enough sample text')}
it "renders :new template" do
post :create, quotation_request: invalid_data
expect(response).to render_template(:new)
end
it "doesn't creates new quotation_request in database" do
expect {
post :create, quotation_request: invalid_data
}.not_to change(QuotationRequest, :count)
end
end
end
describe "DELETE destroy" do
let(:quotation_request) { FactoryGirl.create(:quotation_request) }
it "redirects to the quotation request#index" do
delete :destroy, id: quotation_request
expect(response).to redirect_to(quotation_requests_path)
end
it "delets the quotation request from the database" do
delete :destroy, id: quotation_request
expect(QuotationRequest.exists?(quotation_request.id)).to be_falsy
end
end
end
My quotation_requests_controller.rb
class QuotationRequestsController < ApplicationController
# before_action :authenticate_employee!, :only => [:show]
def index
#quotation_requests = QuotationRequest.all
end
def new
#quotation_request = QuotationRequest.new
#quotation_request.build_client
end
def edit
#quotation_request = QuotationRequest.find(params[:id])
end
def create
client = Client.find_or_create(quotation_request_params[:client_attributes])
#quotation_request = QuotationRequest.new(quotation_request_params.except(:client_attributes).merge(client: client))
if #quotation_request.save
ClientMailer.quotation_request_created(client.email, #quotation_request.id).deliver_now
redirect_to #quotation_request, notice: 'Thank you.'
else
render :new
end
end
def show
#quotation_request = QuotationRequest.find(params[:id])
end
def update
#quotation_request = QuotationRequest.find(params[:id])
if #quotation_request.update(quotation_request_params)
redirect_to #quotation_request
else
render :edit
end
end
def destroy
QuotationRequest.destroy(params[:id])
redirect_to quotation_requests_path
end
private
def quotation_request_params
params.require(:quotation_request).permit(:number_of_words, :return_date, :sample_text, :client_attributes => [:first_name, :last_name, :email])
end
end
I know the routes authentication works because if I test them in the browser I get redirected to the sign_in page. However, the tests don't pass in Rspec.
if I put this code in the quotation_requests_controller.rb:
before_action :authenticate_employee!, :only => [:show]
The rspec tests pass. So for some reason Rspec does not register the authentication of the routes.
Here is the output from Rspec for the tests run with the authenticated routes:
QuotationRequestsController
GET index
valid request
renders :index template for signed in employee
assigns quotation requests to template
invalid request
does not render :index template without a signed in employee (FAILED - 1)
GET edit
valid request
renders :edit template with a signed in employee
assigns the requested quotation request to template
invalid request
does not render the :edit template without a signed in employee (FAILED - 2)
PUT update
valid request
valid data
redirects to quotation_request#showtemplate
updates quotation request in the database
invalid data
renders the :edit template
does not update the quotation_request in the database
invalid request
redirects user to the sign in page (FAILED - 3)
GET new
renders :new template
assigns new QuotationRequest to #quotation_request
GET show
invalid request
does not render :show template if an employee or client is not signed in (FAILED - 4)
valid request
renders :show template if an employee or client is signed in
assigns requested quotation_request to #quotation_request
POST create
valid data
redirects to quotation_requests#show
creates new quotation_request in database
invalid data
renders :new template
doesn't creates new quotation_request in database
DELETE destroy
valid request
redirects to the quotation request#index
delets the quotation request from the database
invalid request
does not delete the quotation request without a signed in employee (FAILED - 5)
Failures:
1) QuotationRequestsController GET index invalid request does not render :index template without a signed in employee
Failure/Error: expect(response).to_not render_template(:index)
Didn't expect to render index
# ./spec/controllers/quotation_requests_controller_spec.rb:43:in `block (4 levels) in <top (required)>'
# -e:1:in `<main>'
2) QuotationRequestsController GET edit invalid request does not render the :edit template without a signed in employee
Failure/Error: expect(response).to_not render_template(:edit)
Didn't expect to render edit
# ./spec/controllers/quotation_requests_controller_spec.rb:92:in `block (4 levels) in <top (required)>'
# -e:1:in `<main>'
3) QuotationRequestsController PUT update invalid request redirects user to the sign in page
Failure/Error: expect(response).to_not redirect_to(quotation_request)
Didn't expect to redirect to #<QuotationRequest:0x007fe7eb69c8c0>
# ./spec/controllers/quotation_requests_controller_spec.rb:182:in `block (4 levels) in <top (required)>'
# -e:1:in `<main>'
4) QuotationRequestsController GET show invalid request does not render :show template if an employee or client is not signed in
Failure/Error: expect(response).to_not render_template(:show)
Didn't expect to render show
# ./spec/controllers/quotation_requests_controller_spec.rb:217:in `block (4 levels) in <top (required)>'
# -e:1:in `<main>'
5) QuotationRequestsController DELETE destroy invalid request does not delete the quotation request without a signed in employee
Failure/Error: expect(QuotationRequest.exists?(quotation_request.id)).to be_truthy
expected: truthy value
got: false
# ./spec/controllers/quotation_requests_controller_spec.rb:361:in `block (4 levels) in <top (required)>'
# -e:1:in `<main>'
Finished in 2.11 seconds (files took 1.75 seconds to load)
23 examples, 5 failures
Failed examples:
rspec ./spec/controllers/quotation_requests_controller_spec.rb:37 # QuotationRequestsController GET index invalid request does not render :index template without a signed in employee
rspec ./spec/controllers/quotation_requests_controller_spec.rb:83 # QuotationRequestsController GET edit invalid request does not render the :edit template without a signed in employee
rspec ./spec/controllers/quotation_requests_controller_spec.rb:171 # QuotationRequestsController PUT update invalid request redirects user to the sign in page
rspec ./spec/controllers/quotation_requests_controller_spec.rb:208 # QuotationRequestsController GET show invalid request does not render :show template if an employee or client is not signed in
rspec ./spec/cont
Why do the routes I have written not work in Rspec tests?
I take it you are using rspec-rails in your rails app.
Rspec-rails sets up a lot of convenience methods for you, but it also introduces some black-magic, which can lead to some unexpected results - like this.
As you can see here it is explained in the comments for controller specs:
# Supports a simple DSL for specifying behavior of ApplicationController.
# Creates an anonymous subclass of ApplicationController and evals the
# `body` in that context. Also sets up implicit routes for this
# controller, that are separate from those defined in "config/routes.rb".
I guess the logic here is, controller features are different from routing and should be tested separately (and indeed rspec-rails offers a test group for routing), so we do not need the routes for controller specs, meaning you should be able to test your controller without setting up the routes.
In my oppinion, testing the redirect for unauthenticated users is more of an integration test, since it requires multiple parts of your application to work together and as such should not be tested in the controller context, but rather as a feature in some blackbox test.
You can write integration tests by placing them in one of these directories spec/requests, spec/api, and spec/integration or by explicitely declaring their type with
RSpec.describe "Something", type: :request do
or place it in spec/features or declare the type as
RSpec.describe "Something", type: :feature do
depending on which level you want to test the redirect (meaning: only test the request-response cycle, or run it in a simulated browser).
Please refer to the documentation for integration tests on the rspec-rails github page for more information.

Minitest-rails with bootstrap editable

Currently doing Rails Unit Test, using minitest-rails.
I'm using bootstrap editable js to directly update data in view.
I having trouble asserting value correctly, got Failure result.
Only for the function that I used bootstrap editable, since it uses other way to send parameters than normal Rails update action.
Please help a look at my codes.
In my controller:
def edit_job_type
update_common_table('job_types', params[:pk], params[:name], params[:value])
end
In my included module:
def update_common_table(table, id, key, value)
begin
case table
when 'job_types'
#record = JobType.find(id)
end
case key
when 'en_name'
#record.en_name = params[:value]
edit_field = 'English Name'
end
#record.last_updated_by = session[:username]
#record.save
render json: {
status: 'success',
message: "#{edit_field} was successfully updated.",
updated_at: #record.updated_at.to_time.strftime("%a, %e %b %Y %H:%M"),
updated_by: session[:username]
}
rescue => error
render json: {status: 'error', message: error.message}
end
end
In my minitest-rails, controller:
setup do
#job_type = job_types(:waiter)
end
test "should update job_type" do
patch :edit_job_type, id: #job_type.id, job_type: { pk: #job_type.id, name: 'en_name', value: "janitor" }
assert_response :success, message: 'English Name was successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name # this one FAILS, not updated value
end
In my fixtures > job_types:
waiter:
en_name: waiter
When I run rake test:
I got failure result, because the update was failed.
Expected: "New Job Type Updated"
Actual: "waiter"
Still getting the default value "waiter", instead of "janitor"
Please help to figure out how can I fixed my test.
SOLVED
Finally I've made a work around after thorough searching.
The solution was to use a XHR method since bootstrap editable uses POST method.
Before:
test "should update job_type" do
patch :edit_job_type, id: #job_type.id, job_type: { pk: #job_type.id, name: 'en_name', value: "janitor" }
assert_response :success, message: ' Successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name # this one FAILS, not updated value
end
After:
test "should update job_type" do
xhr :post, :edit_job_type, format: :js, pk: #job_type.id, name: 'en_name', value: "janitor"
assert_response :success, ' Successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name
end
Thanks to this tutorial, Building Rails Test

RSpec/Rails post test issue with post data

Rails: 4.1.7
RSpec-rails version: 3.1.0
I am trying to write a request spec to test the create action for my BlogPost model. RSpec doesn't seem to like the data params that I am trying to pass in because I keep seeing the following error when running the test:
ActiveRecord::UnknownAttributeError:
unknown attribute: blog_post
RSpec code:
require 'rails_helper'
RSpec.describe BlogPost do
let!(:admin_user) { Fabricate(:admin_user) }
let!(:blog_post) { Fabricate(:blog_post) }
before { login(admin_user.email, admin_user.password) }
describe 'POST /admin/blog_posts' do
before do
post admin_blog_posts_path, blog_post: {
body: 'body text',
title: 'title text',
cover_image: '/assets/post.png',
summary: 'cool post bruh',
live_demo_url: 'livedemo.com',
live_demo_url_text: 'click here',
github_source: 'github.com/awesome'
}
end
it 'should redirect to the blog posts index page' do
expect(response).to redirect_to(admin_blog_posts_path)
follow_redirect!
end
end
end
There is something about using the word blog_post it doesn't seem to like. Because I tried changing it to an arbitrary word like so and the error went away:
post admin_blog_posts_path, someresource: {
title: 'title text'
}
Also I have a put request spec, which is also using blog_post and that works fine:
describe 'PUT /admin/blog_posts/:id' do
before do
put admin_blog_post_path(blog_post.id), blog_post: {
title: 'My new title'
}
end
...
end
So I'm not really sure why RSpec doesn't like my post admin_blog_posts_path, blog_post ... syntax. Any ideas?
Noob status over here.
In my controller create action I had:
#blog_post = BlogPost.new(permitted_params)
It was raising the error because I passed in the entire params so it read blog_post as an attribute for the BlogPost resource.
The fix: #blog_post = BlogPost.new(permitted_params[:blog_post])

Rails 4 testing controller spec ,devise helper sign_in doesn't work

I have Rails 4.2.6 and rspec 3.3.0 , devise version 3.5.8 and i have trouble to test my controller specs always getting error saying ("expected the response to have a success status code (2xx) but it was 401")
Later when i try different spec I am always getting failure message ("Your account is not enabled yet !"), any ideas ?, any help much appreciated ..
my spec:
require 'rails_helper'
describe MyController, :type => :controller
let(:user) { create(:user) }
let(:campaign) { create(:campaign, user: user) }
let!(:placement) { create(:placement, user: user, campaign: campaign, end_date: Date.today) }
before(:each) do
sign_in user
end
context "when we have no data" do
before do
get :graph_data, format: :json
#json = JSON.parse(response.body)
end
it "should be a success" do
expect(response).to have_http_status(:success)
end
end
end
I have following spec_helper:
config.include Devise::TestHelpers, :type => :controller
config.before(:each) do
DatabaseCleaner.start
end
factory defined:
FactoryGirl.define do
factory :unconfirmed_user, class: User do
sequence(:email) { |n| "john#{n}#email.com" }
sequence(:name) { |n| "John Nice #{n}" }
password 'password'
password_confirmation 'password'
factory :user do
confirmed_at Time.now.utc
factory :admin do
organisation { create(:organisation, :some_organisation) }
end
end
end
controller:
MyController < ApplicationController
def graph_data
items = current_user.items //outputs [1,2,3,45,5]
render json: items, status: :ok
end
end
routes:
GET /mycontroller/graph_data(.:format)
i found what was the issue,on User model had a method 'active_for_authentication?'
def active_for_authentication?
super && (group.include?('company-group) || is_admin? )
end
Method 'active_for_authentication?' is a Devise public method
and when it is defined in your model like (User), you change behaviour of it, and in my case it was overwritten with different behaviour,
checking for belonging user to the 'company-group' or not ..
That's why i had failure message ("Your account is not enabled yet !")