I'm trying to do some integration tests with rspec on rails 4 but I alway get a "ActionController::UnknownFormat" exception when running the tests.
I tried two different ways:
Failure/Error: post sensors_path, sensor: #sensor_attributes.to_json
ActionController::UnknownFormat:
ActionController::UnknownFormat
Failure/Error: post sensors_path, sensor: #sensor_attributes, format: :js
ActionController::UnknownFormat:
ActionController::UnknownFormat
Here is the rspec code:
it "should change the number of sensors" do
lambda do
post sensors_path, sensor: #sensor_attributes.to_json
end.should change(Sensor, :count).by(1)
end
it "should be successful" do
post sensors_path, sensor: #sensor_attributes, format: :js
response.should be_success
end
And this is the create statement of the controller:
def create
respond_to do |format|
format.json do
#sensor = Sensor.new(params["sensor"])
#sensor.uuid = SecureRandom.uuid
#sensor.save
render nothing: true
end
end
end
And the sensor_attributes:
before do
#sensor_attributes = { name: "Testname", description: "This is a Test-Description." }
end
And the routes:
resources :sensors
Any idea what went wrong?
You're using json format in controller, but you're passing format: :js in the test.
It should be:
post sensors_path, sensor: #sensor_attributes, format: :json
This is for future readers.
Somehow the answer did not help me.
Writing like following worked for me -
params = {
sensor: {
name: "Testname",
description: "This is a Test-Description."
},
format: :json
}
post sensors_path, params
Appending .json to the path worked for me as well (since I was using a shared rspec example and could not just append format: json)...
post "#{sensors_path}.json", sensor: #sensor_attributes
it "creates a new sensor" do
expect {
post :create, {:sensor => {here_you_need_to_put_the_variables_which_are_needed_to_create_a_sensor}}
}.to change(Sensor, :count).by(1)
end
In you create method, the following line
#sensor = Sensor.new(params["sensor"])
should be:
#sensor = Sensor.new(params[:sensor])
I don't know the structure of you code. All the solution code i gave is based on my own assumption.
Related
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
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
There is the following code:
describe 'Some title' do
before do
session = ActionController::TestSession.new
session[:state] = "12334"
get '/api/v1/menus', format: :json
end
it 'some text' do
expect(response).to be_success
json = JSON.parse(response.body)
puts json
end
end
Code of the controller:
class Api::V1::MenusController < Api::V1::ApiV1Controller
def index
render json: session
end
end
But controller returns an empty session always. How can I fix it?
Try adding this:
describe 'Some title', :type => :controller do
And remove session = ActionController::TestSession.new.
RSpec needs to know you are doing "controller things" in your test. You indicate this as above or by placing the test in spec/controllers.
There is the following spec:
describe 'Some title' do
before do
session[:state] = "12334"
get '/api/v1/menus', format: :json
end
it 'some text' do
expect(response).to be_success
json = JSON.parse(response.body)
puts json
end
end
It code tests the following controller's action:
class Api::V1::MenusController < ActionController
def index
render json: session
end
end
But I've got the following exception: "undefined method `session' for nil:NilClass". How can I fix it? How can I make a new example of a session? Thanks in advance.
Try this:
describe 'Some title', :type => :controller do
RSpec needs to know you are doing "controller things" in your test. You indicate this as above or by placing the test in spec/controllers.
I need to know how to test this controller action
def create_mobile
if mobile_user = MobileUser.authenticate(params[:email], params[:password])
session[:mobile_user_id] = mobile_user.id
respond_to do |format|
format.json { head :ok }
end
else
respond_to do |format|
format.json { head :unauthorised }
end
end
end
The route is a post request to sessions/create_mobile and as you can see the action only responds to json
My current controller spec looks like this
describe SessionsController, "Logging in" do
before(:each) do
#mobile_user = FactoryGirl.create(:valid_mobile_user)
end
it "Should log in mobile user" do
#request.env["HTTP_ACCEPT"] = "application/json"
post :create_mobile, {:password => #mobile_user.password, :email => #mobile_user.email}
response.should be_success
end
it "should fail log in mobile user" do
#request.env["HTTP_ACCEPT"] = "application/json"
post :create_mobile, {:password => 'joe', :email => #mobile_user.email}
response.should_not be_success
end
end
The test results in
1) SessionsController Logging in should log in mobile user
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/sessions_controller_spec.rb:11:in `block (2 levels) in <top (required)>'
So there is either a problem with my application code or there is a problem with my test code but either way there is no problem with the authenticate method as the MobileUser model spec passes which looks like this
it "should authenticate" do
mu = FactoryGirl.create(:valid_mobile_user)
assert_equal 1, MobileUser.count
assert_equal mu, MobileUser.authenticate(mu.email, mu.password)
end
Any help in sorting this out greatly appreciated.
Thanks in advance
UPDATE
As suggested below, using
post :create_mobile, {:password => 'joe', :email => #mobile_user.email} :format => :json
or using
#request.env["HTTP_ACCEPT"] = "application/json"
or a combination of both makes no difference
UPDATE 2
The test has just started working for no reason that I can fathom (other than I never understood why it wasn't working in the first place).
Totally strange!
I met the same issue recently. below is my test code
post :create, :login => 'mock_user', :password => 'passwd', :format => :json
expected = {
:login => 'mock_user'
}.to_json
session[:user_id].should == mock_user.id
response.body.should == expected
The test result is false, caused by response.body is blank. I checked the test.log, and found got 406 unacceptable request error in controller. After googled a lot, I changed to :format => 'json'. Then I got the expected test result.
Since you're trying to post JSON to the controller, shouldn't you convert your hash to JSON?
post :create_mobile, {:password => 'joe', :email => #mobile_user.email}.to_json
If you don't want to add seven characters to your code just to see if it works, then you should output your params to logger to see what they look like.
def create_mobile
logger.info "LOGIN PARAMS U/N: #{params[:email]}, P/W: #{params[:password]}"
...
Then tail -f log/test.log to see what your controller looks like during your test.
have you tried adding a :format option to the post statement