RSpec testing: post multiple params to methods - ruby-on-rails

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.

Related

In RSpec, running a patch or put test will result in an ActionController::UrlGenerationError: No route matches error

What I want to solve
I want the Rspec patch or put test to succeed.
I also tested PostsContoroller before this, and I am puzzled because I did not get the same error when testing PostsContoroller.
Error
Failures:
1) Api::V1::PostItemsController update Update Content
Failure/Error: patch :update, params: { post: post_params }
ActionController::UrlGenerationError:
No route matches {:action=>"update", :controller=>"api/v1/post_items", :post=>{:id=>1, :content=>"Update-Content", :status=>false, :post_id=>1}}
# ./spec/controllers/post_items_spec.rb:11:in `block (3 levels) in <main>'
Finished in 0.35529 seconds (files took 5.58 seconds to load)
5 examples, 1 failure
Code
FactoryBot
book.rb
FactoryBot.define do
factory :book, class: Post do
sequence(:id) { |n| n}
sequence(:title) { |n| "title#{n}" }
sequence(:author) { |n| "author#{n}" }
sequence(:image) { |n| "image#{n}"}
end
end
content.rb
FactoryBot.define do
factory :content, class: PostItem do
sequence(:id) { |n| n }
sequence(:content) { |n| "list#{n}"}
sequence(:status) { false }
end
end
Spec
post_items_spec.rb
require 'rails_helper'
RSpec.describe Api::V1::PostItemsController, type: :controller do
describe 'update' do
it 'Update Content' do
book = create(:book)
content = create(:content, post_id: book.id)
post_params = { id: content.id, content: 'Update-Content', status: false, post_id: book.id }
patch :update, params: { post: post_params }
json = JSON.parse(response.body)
expect(response.status).to eq(200)
expect(json['Update-Content']).to eq('Update-content')
end
end
end
Routes
**Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :posts
resources :post_items
end
end
end
The use of controller specs is discouraged by both the Rails and RSpec teams and has been for a very long time now. You should be writing a request spec instead which sends real HTTP requests.
RSpec.describe 'Api V1 Post items', type: :request do
let(:book) { create(:book) }
describe "PATCH /api/v1/books" do
context "with valid parameters" do
subject do
patch api_v1_post_item_path(book),
params: { content: 'Update-Content' }
end
it { should be_successful }
it "updates the content" do
# refresh the record from the db
expect { book.reload }.to change(book, :title).to('Update-Content')
end
it "includes the updated entity in the response body" do
expect(response.parsed_body['content']).to eq 'Update-Content'
end
end
# #todo write specs with invalid parameters
# #todo write specs for authentication and authorization
end
end
Another problem is that you're generating IDs in your factory. Do not do this ever. When you're actually persisting records the database will automatically assign ids. When you use build_stubbed FactoryBot will create a mock id. Using a sequence to generate IDs invites bad practices such as hardcoding ids into a spec and will only cause you headaches.
If you really want to salvage that controller spec the routing error is caused by the fact that you're missing an the ID parameter - since you're calling it as patch :update, params: { post: post_params } the id parameter is buried in params[:post][:id]. So you want patch :update, params: { id: post.id, post: post_params } I don't recommend this though - get with the program and write future proof tests instead that won't let all the bugs slip though.

Error occuring in testing my controller with RSPEC using shoulda matchers especially create i can't able to test save functionality

I am testing my controller with RSPEC using shoulda matchers while i came across the create method in my controller i cant test the save function if i try to do that i go the error
Expected response to be a <3XX: redirect>, but was a <200: OK>
i have attached my controller part and testing and route
In testing
RSpec.describe "routes for home", type: :routing do
describe 'post #create' do
before do
post :create , params: params
end
context 'when the params are correct' do
let(:params) { { restaurant: { restaurantname: "Buhari" ,location_id: 1} } }
it 'is expected save successfully and redirect_to gridpage' do
expect(assigns[:restaurant].save).to redirect_to(gridurl_path)
end
end
end
end
In controller
def create
# render plain: params
#restaurant=Restaurant.new(restaurant_params)
if #restaurant.save
redirect_to gridurl_path
else
render 'index'
end
end
In routes
post "/home/create", to: "home#create", as: :createurl
get '/home/grid', to: 'home#grid',as: :gridurl
Thank you in advance
First I suggest you read https://relishapp.com/rspec/rspec-rails/docs/controller-specs and also the other docs. They will give you a good starting point on how to test stuff with rspec.
When you look at a controller action, you are not interested on who's doing what (i.e assigns[:restaurant]) - you want to see if a redirect happens, if something is saved in the DB, etc. Think of it from the perspective of a user calling that endpoint. Does the user know all of the internals?
Here is how it should look like:
describe "routes for home", type: :controller do
describe 'post #create' do
context 'when the params are correct' do
let(:params) { { restaurant: { restaurantname: "Buhari" ,location_id: 1} } }
it 'is expected save successfully and redirect_to gridpage' do
post :create, params: params
expect(response).to redirect_to('/home/grid')
end
end
end
end

test update request with RSpec failed

I have two problems when I try to test the update action with RSpec, here is the controller file:
#volunteers_controller.rb
module Api
module V1
class VolunteersController < ApplicationController
before_action :find_volunteer, only: %i[show update destroy]
def update
#volunteer.update!(volunteer_params)
head :no_content
end
private
def find_volunteer
#volunteer = Volunteer.find_by!(id: params[:id])
end
def volunteer_params
params.require(:volunteer).permit(:image_url, :name, :job_desc)
end
end
end
end
Here is the test file:
require 'rails_helper'
RSpec.describe Api::V1::VolunteersController, type: :request do
...
describe '#update' do
let(:volunteer) { Volunteer.create!( :image_url=>"first.jpg", :name=>"test1", :job_desc=>"description") }
let(:params){
{:volunteer => {
"image_url"=>"new.jpg",
"name"=>"test1",
"job_desc"=>"description"
}
}
}
it 'updates a certain volunteer' do
patch :patch, :params => params #failed, bad URL
expect(volunteer.image_url).to eq("new.jpg") #failed, still return 'first.jpg'
end
it 'returns a no_content header' do
patch "http://localhost:3000/api/v1/volunteers/#{volunteer.id}", :params => params
expect(response).to have_http_status "204"
end
end
end
private
def json_parse(string)
if string.class==String
json = JSON.parse(string)
end
json
end
So my questions are:
when try to write the URL like this: patch :patch, :params => params, I got the following error:
Api::V1::VolunteersController#update updates a certain volunteer
Failure/Error: patch :patch, :params => params
URI::InvalidURIError:
bad URI(is not URI?): "http://www.example.com:80patch"
How can I change the URL to: "http://localhost:3000/api/v1/volunteers/#{volunteer.id}"?
I manually test the update action, putting a binding.pry in the update action, it does update volunteer subject, however, when it goes back to the test, it shows that it doesn't not get updated, why is that?
Thank you!!
The first problem is really your update method itself and its complete lack of error handling and meaningful feedback to the client. update! will raise ActiveRecord::RecordInvalid if the input is invalid - which is not rescued at all in your controller. And exceptions should no be used for normal code flow - invalid input is not really an exceptional event.
Instead you should rewrite your controller so that it checks if the update is performed and returns the appropriate response:
def update
if #volunteer.update(volunteer_params)
head :no_content
else
head :unprocessable_entity
end
end
As for the spec itself you're mixing up controller specs and request specs. While they look somewhat similar the key difference is that a request spec sends actual HTTP requests your rails server while a controller spec stubs the actual request and passes it to an instance of the controller under test.
In a controller spec you could write:
patch :update, params: { ... }
Because its actually calling the update method on an instance of the controller. But of course:
patch :patch, :params => params #failed, bad URL
Will not work in request spec since its not a valid URL and request specs send actual HTTP requests. Note that you should pass relative URLs and not absolute URLs as the test server may run on a different port then the dev server
# Bad
patch "http://localhost:3000/api/v1/volunteers/#{volunteer.id}", :params => params
# Good
patch "/api/v1/volunteers/#{volunteer.id}", params: params
ActiveRecord models are not "live reloading" - the representation in memory will not automatically be updated when the values in the database are updated. You need to manaully reload the record for that to happen:
it 'updates a certain volunteer' do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
volunteer.reload
expect(volunteer.image_url).to eq("new.jpg")
end
Altogether your spec should actually look something like:
# Describe the endpoint - not the controller implmentation
RSpec.describe "V1 Volunteers API", type: :request do
describe 'PATCH /api/v1/volunteers/:id' do
# use do ... end if the expression does not fit on one line
let(:volunteer) do
# enough with the hashrockets already!
Volunteer.create!(
image_url: "first.jpg",
name: "test1",
job_desc: "description"
)
end
context "with invalid parameters" do
# some set of failing parameters
let(:params) do
{
volunteer: {
name: ""
}
}
end
it "returns unproccessable entity" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect(resonse).to have_http_status :unproccessable_entity
end
it "does not update the volunteer" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect { volunteer.reload }.to_not change(volunteer, :name).to("")
end
end
context "with valid parameters" do
# some set of failing parameters
let(:params) do
{
volunteer: {
image_url: "new.jpg",
name: "test1",
job_desc: "description"
}
}
end
it "returns no content" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect(resonse).to have_http_status :no_content
end
it "updates the volunteer" do
patch "/api/v1/volunteers/#{volunteer.id}", params: params
expect { volunteer.reload }.to change(volunteer, :image_url)
.to("new.jpg")
end
end
end
end

How to test "Model.valid?" on a FactoryBot generated model using Rspec?

I have created a Chatroom Model that first validates for the presence of some fields so in the controller I create the chatroom then check if it is valid by using the .valid? method to determine the response. Now when I created a test model using FactoryBot the test doesn't go past the if statement and it returns a response as if the test has finished.
Code for my action
def create
new_chatroom = Chatroom.create(chatroom_params)
if new_chatroom.valid?
new_chatroom.members.create({ user_id: #current_user[:username] })
render_response(new_chatroom, :created)
else
render_error_response(new_chatroom.errors, :bad_request)
end
end
Code for the factory
FactoryBot.define do
factory :chatroom do
topic { Faker::Lorem.unique.question }
slug { Faker::IndustrySegments.unique.sub_sector }
description { Faker::Lorem.paragraph }
owner { Faker::Name.first_name }
public { true }
end
end
Here is my test
it "creates a new chatroom" do
post :create, params: {
:topic => "test chatroom",
:slug => "code-testing",
:description => "Testing with Rspec",
}
expect(response).to have_http_status(:created)
end
Here is the render_response method:
def render_response(resource, status)
if block_given?
yield(resource, status)
else
render json: resource, :status => status
end
end
Test failure:
Failure/Error: expect(response).to have_http_status(:created)
expected the response to have status code :created (201) but it was :ok (200)
I get this failure and when I try to make it pass(false positive), the coverage shows the response I'm testing against is not what's actually in my action because the rest of the lines starting from the if statement are not covered.
but I thought FactoryBot takes over the whole model creation in the
tests.
No - FactoryBot just provides factories that create model instances. This is widely used as a replacement for fixtures to populate the database before tests. Unlike with fixtures this is not automatic.
Just adding FactoryBot changes absolutely nothing in your application besides the fact that the generators will create the factory file. It does not effect the behaviour of your models in any way.
When testing the creation of resources you need to test that:
Given valid params, then a model should be persisted to the database
Given valid params, then the response should be successful and point to the newly created resource.
Given invalid params, then a model should not be persisted to the database
Given invalid params, then the response should be 422 and an error page should be rendered.
You want to test this with a request spec and not a controller spec.
Request specs provide a high-level alternative to controller specs. In
fact, as of RSpec 3.5, both the Rails and RSpec teams discourage
directly testing controllers in favor of functional tests like request
specs.
require "rails_helper"
RSpec.describe "Chatroom creation", type: :request do
let(:valid_params) do
{
chatroom: {
topic: "test chatroom",
slug: "code-testing",
description: "Testing with Rspec"
}
}
end
let(:invalid_params) do
{
chatroom: {
topic: ''
}
}
end
context "when the parameters are valid" do
it "creates a new chatroom" do
expect do
post '/chatrooms', params: valid_params
end.to change(Chatroom, :count).by(1)
end
it "returns success" do
post '/chatrooms', params: valid_params
expect(response).to have_http_status(:created)
end
end
context "when the parameters are invalid" do
it "does not create a new chatroom" do
expect do
post '/chatrooms', params: invalid_params
end.to_not change(Chatroom, :count)
end
it "returns bad entity" do
post '/chatrooms', params: invalid_params
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
Then we can address the problem with your controller which should read:
class ChatroomsController < ApplicationController
# ...
def create
new_chatroom = Chatroom.new(chatroom_params)
if new_chatroom.save
new_chatroom.members.create(user_id: #current_user[:username])
render_response(new_chatroom, :created)
else
render_error_response(new_chatroom.errors, :bad_request)
end
end
end
You should never use .valid? to check if a record was saved to the database. It only ensures that the model level validations have passed. Not that the INSERT statement from .create actually created a row in the database. See The Perils of Uniqueness Validations for an example of what can happen.
While you can use new_chatroom.persisted? this is the common rails idiom since it gives you a variable that you can manipulate before the record is persisted.
class ChatroomsController < ApplicationController
# ...
def create
new_chatroom = Chatroom.new(chatroom_params)
new_chatroom.members.new(user: current_user)
if new_chatroom.save
render_response(new_chatroom, :created)
else
render_error_response(new_chatroom.errors, :bad_request)
end
end
end
I would not try to test model validity in a controller spec, but rather write request specs (as suggested by the other respondent). You can test the validity of an object on the model itself like this:
context 'valid' do
let(:chatroom) { create :chatroom }
it { expect(chatroom).to be_valid }
end
context 'fails validation' do
let(:chatroom) { create :chatroom, topic: nil }
it { expect(chatroom).not_to be_valid }
end
Resource: https://www.rubydoc.info/gems/rspec-rails/RSpec%2FRails%2FMatchers:be_valid
But if you want to check that actual fields are validated, I recommend using shoulda matchers on the model like this:
it { should validate_presence_of :topic }
it { should validate_presence_of :slug }
Resource: https://github.com/thoughtbot/shoulda-matchers

Why is RSpec not passing my headers to Rails?

I am trying to test my Rails application using RSpec, but my tests are failing because RSpec seems to not be passing the headers I give it to Rails.
I have a UsersController that includes ApplicationHelper, and in ApplicationHelper I have a method that accesses the headers hash. Indexing it by my SESSION_KEY header returns nil. If I puts headers inside that method, the hash does not contain the header I have supplied, only the following: {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff"}.
Here is the relevant part of my RSpec spec:
require 'rails_helper'
describe Api::V1::UsersController, type: :request do
let(:user) { User.create(name: 'TestUser', email: 'someone#example.com', password: 'password123', password_confirmation: 'password123') }
let(:id) { user.id }
let(:sess) { user.sessions.create }
before { get "/api/v1/users/#{id}" }
# Snipped other tests
context 'with authentication' do
context 'with a valid id' do
it 'returns full user information' do
get "/api/v1/users/#{id}", nil, {'HTTP_SESSION_KEY': sess.key}
response_user = response_json[:user]
expect(response.status).to eq 200
expect(response_user).to_not be_nil
expect(response_user[:name]).to eq user[:name]
expect(response_user[:email]).to eq user[:email]
end
end
end
def response_json
JSON.parse(response.body, symbolize_names: true)
end
end
I have also tried passing the SESSION_KEY header without HTTP_ before it, and that did not work. I have also tried moving it up to the top get in the before block to see if it was a context issue, and that did not work either.
Docs say the above should work, but if for some reason rspec is interpreting your test as a :controller test and not a :request test then you need to do this (just before your get call):
request.env["HTTP_SESSION_KEY"] = sess.key

Resources