rails rspec testing nested create action - ruby-on-rails

Can someone please help me to figure out how to test nested create action.
Controller.rb
def create
#itemline = V1::ItemLine.new(:net_price => params[:net_price])
#v1_product = #itemline.build_product(v1_product_params)
#v1_product.save
end
def v1_product_params
params.require(:v1_product).permit(:name, :net_price, :item_line => [:quantity, :net_price])
end
Controller_spec.rb
describe "with valid params" do
it "creates a new V1::Product" do
expect {
post :create, :v1_product => {name: "test", net_price: 10, item_line_id: 1 }, token: #user.api_key.token
}.to change(V1::Product, :count).by(1)
expect(V1::Product.last.name).to eq "test"
expect(V1::Product.last.net_price).to eq 10
expect(V1::Product.last.item_line_id).to eq 1 #FAILS
end
It does create the product, but it doesn't build the V1::ItemLine.new. Can anyone see what is the problem ?
Thank you

As I see from your code
#itemline = V1::ItemLine.new(:net_price => params[:net_price])
is not correct, because :net_price is nested in :v1_product hash
params.require(:v1_product).permit(:name, :net_price, :item_line => [:quantity, :net_price])
Try
#itemline = V1::ItemLine.new(:net_price => params[:v1_product][:net_price])

Related

Testing destroy with RSpec

I have this in controllers
def destroy
#post = Post.find(params[:id])
#post.destroy
end
But I'm lost as to how to actually test if it works. Any pointers would be highly appreciated! I currently have this in my RSpec file:
require 'rails_helper'
RSpec.describe Post, type: :model do
it "must have a title" do
post= Post.create
expect(post.errors[:title]).to_not be_empty
end
it "must have a description" do
post= Post.create
expect(post.errors[:description]).to_not be_empty
end
it "must have a location" do
post= Post.create
expect(post.errors[:location]).to_not be_empty
end
it "must have an image" do
post= Post.create
expect(post.errors[:image]).to_not be_empty
end
it "can be destroyed" do
post= Post.destroy
end
end
You can check if the count of thing has change by -1, like this:
expect { delete '/things', :thing => { :id => 123'} }.to change(Thing, :count).by(-1)
This means that you want to have one less 'thing' and and is ensuring that something has been deleted.
If you want to ensure that specific "thing" was deleted, you can create one before the test, pass the "thing" id as param, and ensure that this doesn't exists on database, like this:
thing = create(:thing)
delete '/things', :thing => { :id => thing.id'}
expect(Thing.find_by(id: thing.id)).to be_nil
As pointed out, if you use request specs ( see https://relishapp.com/rspec/rspec-rails/v/3-9/docs/request-specs/request-spec ) you can easily call the API that should delete the model, and then do an ActiveRecord query to expect no results.
require "rails_helper"
RSpec.describe "delete thing api" do
it "deletes thing" do
// Create a thing with a factory of your choice here
delete "/things", :thing => {:id => 1}
expect(Thing.all.count).to be 0
end
end

rspec controller testing, Expected response to be a <redirect>, but was <200>

I'm using rspec and Factory Girl for testing. When testing the POST #create section of my posts_controller I'm getting the error in the title.
Failures:
1) PostsController POST #create with valid attributes redirects to the post
Failure/Error: response.should redirect_to Post.last
Expected response to be a <redirect>, but was <200>
# ./spec/controllers/posts_controller_spec.rb:59:in `block (4 levels) in <top (required)>'
This is the code from the spec being tested. I'm sure it's not the most efficient way of doing this, but it does work.
def create
#post = Post.new(
:text => post_params[:text],
:embed => post_params[:embed],
:user => current_user,
:title => post_params[:title],
:tag_list => post_params[:tag_list],
:cagegory_ids => post_params[:category_ids]
)
if #post.save
redirect_to #post
else
render 'new'
end
end
...
private
def post_params
params.require(:post).permit(:title, :text, :embed, :user_id, :tag_list,
:category_ids => [])
end
Here's the factory.
FactoryGirl.define do
factory :post do
title { Faker::Lorem.characters(char_count = 20) }
text { Faker::Lorem.characters(char_count = 150) }
user
categories {
Array(5..10).sample.times.map do
FactoryGirl.create(:category)
end
}
end
end
And the relevant part of the spec
describe "POST #create" do
context "with valid attributes" do
it "saves the new post" do
expect{
post :create, post: FactoryGirl.create(:post).attributes
}.to change(Post,:count).by(1)
end
it "redirects to the post" do
post :create, post: FactoryGirl.create(:post).attributes
response.should redirect_to Post.last
end
end
end
The other test, "saves the new post," works fine. I've tried other variations of the redirect_to line, such as "redirect_to(posts_path(assigns[:post])) and it throws the same error.
Any ideas?
OK, I fixed my problem. It isn't pretty but it works.
The issue is with Factory Girl and associations, and I'm definitely not the first to have that problem, but none of the other solutions worked.
I ended up adding this before :each at the top of the POST #create section...
describe "POST #create" do
before :each do
Post.destroy_all
#cat = FactoryGirl.create(:category)
#newpost = FactoryGirl.build(:post)
#post_params = {category_ids: [#cat.id]}.merge(#newpost.attributes)
end
...
...to put the post params into a new hash that I could call later on in the code like this...
it "saves the new post" do
expect{
post :create, post: #post_params
}.to change(Post,:count).by(1)
end
it "redirects to the post" do
post :create, post: #post_params
response.should redirect_to Post.last
end
So this is solved. It adds a bit of overhead to the test but it works. I won't mark this as THE solution for a couple of days in case someone else comes around with some better, simpler code. I definitely welcome any more ideas.
I assume the problem is in factory. Most likely post instance didn't pass validations and controller renders new view, which is not redirect, but success 200. Add logger in the controller and take a look if record actually saves. You can also read through test log tail -f log/test.log.

Session is always empty using RSpec

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.

Rspec 2.7 access controller session in spec before making request

I'm testing my controllers using Rspec and I can't seem to set the session variable of the current controller under test before making the request to the path.
For example this works:
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
get controller_path
request.session[:state] = "12334"
end
end
This doesn't work (i get an error saying session is not a method of Nil class):
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
request.session[:state] = "12334"
get controller_path
end
end
Any ideas?
With new version of RSpec this is done pretty nice, look:
describe SessionController do
# routes are mapped as:
# match 'login' => 'session#create'
# get 'logout' => 'session#destroy'
describe "#create" do
context "with valid credentials" do
let :credentials do
{ :email => 'example#gmail.com', :password => 'secret' }
end
let :user do
FactoryGirl.create(:user, credentials)
end
before :each do
post '/login', credentials
end
it "creates a user session" do
session[:user_id].should == user.id
end
end
# ...
end
describe "#destroy" do
context "when user logged in" do
before :each do
get "/logout", {}, { :user_id => 123 } # the first hash is params, second is session
end
it "destroys user session" do
session[:user_id].should be_nil
end
# ...
end
end
end
You can also use simply request.session[:user_id] = 123 inside before(:each) block, but above looks pretty nicer.
Try this:
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
session[:state] = "12334"
get controller_path
end
end

Dynamically generating shared examples in RSpec 2?

I'm trying to keep my specs DRY by creating a shared example group that performs the boilerplate checks for all admin controllers (all controllers under the Admin namespace of my project). I'm struggling to figure out how to do it, since the shared example needs providing with the information about what actions and parameters to use. It should ideally present meaningful errors if a test fails (i.e. include the details of the action it was testing).
require 'spec_helper'
shared_examples "an admin controller" do
before(:each) do
#non_admin = User.make
#admin = User.make(:admin)
end
context "as an admin user" do
#actions.each do |action, params|
specify "I should be able to access ##{action.last} via #{action.first}" do
self.active_user = #admin
send(action.first, action.last, params)
response.status.should be_ok
end
end
end
context "as a regular user" do
#actions.each do |action, params|
specify "I should be denied access to ##{action.last}" do
self.active_user = #non_admin
send(action.first, action.last, params)
response.status.should be 403
end
end
end
end
describe Admin::UserNotesController do
#user = User.make
#actions = { [:get, :index] => { :user_id => #user.id },
[:get, :new] => { :user_id => #user.id },
[:post, :create] => { :user_id => #user.id } }
it_behaves_like "an admin controller"
end
This errors for the obvious reason that #actions is not visible to the shared example group. If I use let, this is only available in the context of an example, not in the context of the describe block. Any ideas?
Here's a much cleaner way that should work:
require 'spec_helper'
shared_examples "an admin controller" do |actions|
context "as an admin user" do
actions.each_pair do |action, verb|
specify "I should be able to access ##{action} via #{verb}" do
send(verb, action, :user_id => User.make(:admin).id)
response.status.should be_ok
end
end
end
context "as a regular user" do
actions.each_pair do |action, verb|
specify "I should be denied access to ##{action}" do
send(verb, action, :user_id => User.make.id)
response.status.should be 403
end
end
end
end
describe Admin::UserNotesController do
it_behaves_like "an admin controller", {
:index => :get,
:new => :get,
:create => :post
}
end
See http://relishapp.com/rspec/rspec-core/v/2-6/dir/example-groups/shared-examples for more information

Resources