rspec-rails provides scaffold generators for request specs. It generates following code:
RSpec.describe "/widgets", type: :request do
# ...
describe "POST /create" do
context "with valid parameters" do
it "creates a new Widget" do
expect {
post widgets_url, params: { widget: valid_attributes }
rspec -f d output is following
/widgets
POST /create
with valid parameters
creates a new Widget
So such route as POST /widgets/create actually doesn't exist whereas POST /widgets exists.
What is the scheme behind this kind of description? Is there formal definition of this approach?
P.S. Only explanation I can provide is that scheme is Model —> HTTP verb —> /CRUD action but it seems to have not much sense.
So here the type of RSpec test is request so
post => http verb
widgets_url => is request_path or request_url of create method
params => you pass attributes for post http method
If you use type of RSpec is controller then you have to write this
You need to use method name instead of request_path or request_url
RSpec.describe WidgetsController, type: :controller do
describe "POST #create " do
context "with valid parameters" do
it "creates a new Widget" do
expect {
post :create, params: { widget: valid_attributes }
}.to eq("")
end
end
end
end
Related
I have a request spec and I wanted to test CRUD.
The problem is whatever request I put first succeeds, but the rest that follows fail. (Meaning, in the case below, GET will succeed, but POST will fail. If
I switch the 2 context blocks, POST then succeeds, then GET fails).
The failing test says the response is 401 Unauthorized. I'm not sure if the api_key suddenly becomes invalid or something. Or it has something to do with the role I assigned it. (I assigned a system_admin role to the user to be able to CRUD via cancancan)
The way that makes it all work is if I put all requests in one big it block (which I think is not good practice since it returns only 1 passing example, when in reality I have more than 1.)
I've also tried signing in my user again in each block but still the same thing happens.
I am very new to RSpec so any help would be appreciated. Thank you very much!
require 'rails_helper'
RSpec.describe "Departments", type: :request do
user = FactoryBot.build(:user)
role = FactoryBot.build(:role, name: "system_admin")
user.add_role(role.name)
headers = { "x-app-api-key" => user.api_key }
before do
sign_in user
end
describe "CRUD /v1/departments" do
context "GET" do
it "called get" do
get api_departments_path, headers: headers
expect(response.content_type).to eq("application/json; charset=utf-8")
expect(response).to have_http_status(:success)
end
end
context "POST" do
it "called post" do
post api_departments_path, headers: headers, params: { name: "department" }
expect(response.content_type).to eq("application/json; charset=utf-8")
expect(response).to have_http_status(:success)
end
end
end
end
There are a LOT of problems with this spec. First off the bat is your use of local variables instead of let/let!:
require 'rails_helper'
RSpec.describe "Departments", type: :request do
user = FactoryBot.build(:user)
role = FactoryBot.build(:role, name: "system_admin")
user.add_role(role.name)
The problem with this is that these variables will be set when RSpec sets up the spec and not for each example. So provided you actually did insert the records into the database they will still be wiped after the first example is run and all the subsequent examples will fail.
So lets fix that problem:
require 'rails_helper'
RSpec.describe "Departments", type: :request do
let(:user) { FactoryBot.create(:user) }
let(:role) { FactoryBot.create(:role, name: "system_admin") }
before do
user.add_role(role.name)
end
let is evaluated once per example and then memoized.
Note the use of create instead of build which saves the record in the database so that it can actually be accessed from the controller.
Then there is the problem that these tests don't actually provide any value or describe the behavior of the application that you're testing besides testing the http response code.
Test your application - not your tests. If the failure message doesn't tell you anything about what part of the application doesn't work the test is near worthless.
require 'rails_helper'
RSpec.describe "Departments", type: :request do
let(:user) { FactoryBot.create(:user) }
let(:role) { FactoryBot.create(:role, name: "system_admin") }
let(:headers) do
{ "x-app-api-key" => user.api_key }
end
let(:json) { response.parsed_body }
before do
user.add_role(role.name)
sign_in user
end
describe "GET /v1/departments" do
let(:departments) { FactoryBot.create_list(:department, 5) }
it "responds with a list of the departments" do
get api_departments_path, headers: headers
expect(response).to be_successful
# #todo ensure the JSON response contains the expected departments
end
end
describe "POST /v1/departments" do
context "with invalid parameters" do
let(:params) { { name: "" } } # this should just be an invalid or incomplete set of attributes
it "doesn't create a department" do
expect do
post api_departments_path, headers: headers, params: params
end.to_not change(Department, :count)
expect(response).to have_http_status(:unprocessable_entity)
end
end
context "with valid parameters" do
let(:params) { FactoryBot.attributes_for(:department) }
it "creates a department" do
expect do
post api_departments_path, headers: headers, params: params
end.to change(Department, :count).by(1)
expect(response).to have_http_status(:created)
# #todo test that either a JSON representation is returned
# or that the response contains a location header with the URI
# to the resource
end
end
end
end
Testing the content type is largely superflous as you set the content type in the defaults for an API app and testing the JSON responses would fail if its not returning application/json.
I need authentication to pass the test successfully.
describe PostsController do
let(:user) { create(:user) }
describe 'POST #create' do
let(:create_post) { post :create, params: { post: attributes_for(:post) } }
before do
post sessions_path, params: { login: user.email, password: user.password }
end
context 'with valid attributes' do
it 'return OK' do
create_post
expect(response).to have_http_status(200)
end
end
end
post session_path - this does not work and generates an error:
No route matches {:action=>"/sessions", :controller=>"posts", :login=>"jack#example.com", :password=>"qwerty"}
How do I change default PostsController to SessionsController in the before block?
In controller specs you talk only to controller under test. If you need other controllers to create a few prerequisite objects first - create them directly in the db. If you need authenticated user - set session[:user_id] directly (or however it is your authentication works). See this answer, for example.
What you attempted to do is called a "feature spec" (or "integration spec"). It's a different type of spec and you should write some of those as well.
I am using RSpec to test my Rails 4 application and I want to post a "multiple select" param. The params method is like this:
def general_mailing_params
params.require(:mailing).permit({:receivers => []}, :subject, :content)
end
As you can see the receivers param is a multiple select, how can I post this sort of params in RSpec test?
In RSpec controller and request specs you can simply pass arrays and hashes to create any given params hash.
Controller (functional) spec:
require 'rails_helper'
describe MailingsController do
let!(:receiver) { create(:receiver) }
describe 'POST :create' do
it "has the correct receivers" do
post :create, { mailing: { receivers: [receiver.id] } }
expect(Post.last.receivers).to eq [receiver]
end
end
end
Request (integration) spec:
require 'rails_helper'
describe 'Mailings' do
let!(:receiver) { create(:receiver) }
describe 'POST /mailings' do
it "has the correct receivers" do
post '/mailings', { mailing: { receivers: [receiver.id] } }
expect(Post.last.receivers).to eq [receiver]
end
end
end
Note however if you are using the rails collection helpers such as collection_checkboxes properly the param key should be receiver_ids.
I'm a Ruby on Rails developer and I was testing a fairly simple Rails application using RSpec. I was writing some Routing specs and then I faced this problem:
My routes are like this:
Rails.application.routes.draw do
root 'trip_plans#index'
resources :trip_plans
end
So I have a route like post /trip_plans for creating new plans and triggering the trip_plans#create action.
My Routing spec file spec/routing/trip_plans_spec.rb looks like this:
require 'rails_helper'
RSpec.describe 'trip_plans routes', type: :routing do
describe 'creating a new plan' do
it 'creates a new plan on post in /trip_plans' do
expect(post: '/trip_plans').to route_to(controller: 'trip_plans', action: 'create', title: 'New Plan', day: '3')
end
end
end
Now I need to somehow pass the params title: 'New Plan', day: '3' to my expect(post: '/trip_plans') so it seems like a real user is filling in the forms and hitting submit.
How do I pass params for POST requests to my RSpec Routing spec?
Thanks in advance!
Routing specs don't often add much value. In a routing spec you simply test that a certain route matches the correct controller. The controller is never actually called.
Instead what you can use are controller specs which are used to test how your application responds to user input:
# spec/controllers/trip_plans_controller_spec.rb
RSpec.describe TripPlansController, type: :controller do
let(:valid_params) do
{
title: 'New Plan',
day: '3'
}
end
let(:invalid_params) do
{
day: 'xxx'
}
end
describe 'POST #create' do
let(:action) { post :create, valid_params }
context 'with valid attributes' do
it 'creates a new post' do
expect { action }.to change(Post, :count).by(+1)
end
it 'has the correct attrributes' do
action
expect(assigns(:trip_plan).title).to eq 'New Plan'
expect(assigns(:trip_plan).day).to eq 3
end
end
context 'with invalid attributes' do
let(:action) { post :create, invalid_params }
it 'does not create a new post' do
expect { action }.to_not change(Post, :count)
end
it 'renders the new template' do
action
expect(response).to render_template :new
end
end
end
end
and feature specs which are end to end specs which test the actual user experience:
RSpec.feature 'Trip Plans' do
context 'as a User' do
scenario 'I should be able to create a trip plan' do
visit root_path
click_link 'Create a new trip plan'
fill_in 'Title', with: 'Go west'
fill_in 'Day', with: 5
click_button 'Create trip plan'
expect(page).to have_content 'Trip plan created.'
expect(page).to have_content 'Go west'
end
end
end
Controller specs are very useful for testing exactly how your controller responds to params and where you write actual expectations on the database state.
Feature specs are nice since they cover your views as well and well written specs also guarantee that your user paths are accessible. However they often do not catch errors which are not readily apparent from the front end and are slower, since you often need to render several pages to get to the actual meat of the test.
The stack trace or error message from feature specs is often less useful than lower level specs.
A good test suite is usually made of a combination of model specs, controller specs and feature specs which cover the most important paths through the application.
I am trying to test my Grape API, but I am receiving a 400 error in my tests, but when I run the action the test is supposed to test, I get a 201 HTTP response as expected. Not sure what is going on here. Below is the specific RSpec test, but you can view the whole project with the factories and the actual Grape API on GitHub at hackcentral/hackcentral. The test below is testing the POST create action on Alpha::Applications. (app/api/alpha/applications.rb)
describe 'POST #create' do
before :each do
#oauth_application = FactoryGirl.build(:oauth_application)
#token = Doorkeeper::AccessToken.create!(:application_id => #oauth_application.id, :resource_owner_id => user.id)
end
context "with valid attributes" do
it "creates a new application" do
expect{
post "http://api.vcap.me:3000/v1/applications?access_token=#{#token.token}", application: FactoryGirl.attributes_for(:application), :format => :json
} .to change(Application, :count).by(1)
end
it "creates a new application, making sure response is #201" do
post "http://api.vcap.me:3000/v1/applications", application: FactoryGirl.attributes_for(:application), :format => :json, :access_token => #token.token
response.status.should eq(201)
end
end
end
I don't understand why are you testing http://api.vcap.me an not localhost?
You usually test the app on the local enviroment. And this is not the right why to test if the server is working either.
Here is an example of how your test should look like.
https://github.com/dblock/grape-on-rails/blob/master/spec/api/ping_spec.rb from an example project