RSpec Post Not Working - ruby-on-rails

I'm trying to understand why my rspec test isnt working while my actual code to create a new record is.
The error I get is: Failure/Error:
expect {
post "/products/", params: {product: attributes_for(:product)}, as: :json
}.to change(Product, :count).by(1)
expected #count to have changed by 1, but was changed by 0
I tried to actually use the code and it appears to add a new record, but I'm not quite sure why the test is not working. Have I written the test incorrectly?
ProductsController
def create
#p = Product.new(product_params)
#p.save
redirect_to action: :new
end
Product < ApplicationRecord
class Product < ApplicationRecord
validates :title, presence: true #--- Presence
validates :title, length: { minimum: 2 } #--- Minimum 2
validates :title, :description, length: { maximum: 1000 } #--- Maximum 1000
end
ProductSpec
require 'rails_helper'
require 'pp'
RSpec.describe "ProductsRequests", type: :request do
#--- New
describe "#new" do
context "logged in as guest" do
before :each do
get new_product_path
end
it { expect(assigns(:p)).to be_a_new(Product) }
it { expect(response).to render_template :new }
end#--- Guest New
end #--- New
#--- Create
describe "#create" do
context "logged in as guest" do
it "creates new record" do
expect{
post "/products/", params: {product: attributes_for(:product)}
}.to change(Product, :count).by(1)
end
end #--- Guest Create
end #--- Create

The quickest way to debug it would be to change save into save! in your controller.
save tries to save, and returns false if it fails so you probably want to use it and add some handling later (errors are in #p.errors), but for now to quickly see what's going on use save! - it raises an exception if anything (including validations) goes wrong.

Related

RSpec error User must exist with FactoryGirl

I'm creating some test to test a controller and model. When I use FactoryGirl to create fake data I'm getting errors that the User (which the record belongs to) does not exist.
Here is my model composition.rb
class Composition < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :name, presence: true, uniqueness: {scope: :user_id}
end
Here is my FactoryGirl file composition.rb
require 'faker'
FactoryGirl.define do
factory :composition do
name { Faker::Name.name }
description { Faker::Lorem.words }
import_composition { Faker::Boolean.boolean }
import_composition_file { Faker::File.file_name('path/to') }
end
end
This is my the RSpec test that I have until this far
require 'rails_helper'
describe CompositionsController do
before(:each) do
#user = FactoryGirl.create(:user)
#group = FactoryGirl.create(:group)
sign_in #user
#composition = Composition.new(FactoryGirl.create(:composition), user_id: #user.id, group_id: #group.id)
end
describe "GET #index" do
it "renders the index template" do
get :index
expect(assigns(:composition).to eq(#composition))
expect(response).to render_template("index")
end
end
end
Right now I'm getting an error: Validation failed: User must exist, Group must exist
When I don't user FactoryGirl to create a record everything works fine.
Does any body have an suggestion why it's failing?
You don't need to pass FactoryGirl as a param to Model
#composition = FactoryGirl.create(:composition, user: #user, group: #group)
If you don't want to create the record but just want it to initialize, use build instead of create
#composition = FactoryGirl.build(:composition, user: #user, group: #group)

Rspec testing create and redirect with factories and subject

I want to keep my tests as DRY as possible, thus, they look like this:
describe "creating new product" do
subject { FactoryGirl.create(:product) }
it "should increase product count by one" do
expect{ subject }.to change{ Product.count }.by(1)
end
it "and then redirect to product's page" do
expect(subject).to redirect_to(product_path)
end
end
This is my factory:
FactoryGirl.define do
factory :product do
sequence(:title) { |n| "Book#{n}" }
description "Some crazy wibbles, they are fun"
image_url "freddie_mercury.jpg"
price 56.99
end
end
This is my model
class Product < ActiveRecord::Base
validates :title, :description, :image_url, presence: true
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)\Z}i,
message: 'must be a URL for GIF, JPG or PNG image'
}
end
From time to time my first test pass (when I change the name within factory), but after a while I get:
ProductsController creating new product should increase product count by one
Failure/Error: subject { FactoryGirl.create(:product) }
ActiveRecord::RecordInvalid:
Validation failed: Title has already been taken
which is kinda crazy, because I defined the sequence (I tried with do...end blocks, other than 'n' variables - I know it's a bit silly, but nevertheless)
Then, the second test fails as well:
ProductsController creating new product and then redirect to product's page
Failure/Error: expect(subject).to redirect_to(product_path)
ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"products"} missing required keys: [:id]
There I just don't know anymore what to try. Some of my attempts:
expect(subject).to redirect_to(product_path(assign(:product))
expect(subject).to redirect_to(product_path(assign(:product).id)
expect{ subject }.to redirect_to(product_path(assign(:product))
expect(subject).to redirect_to(product_path(subject))
expect(subject).to redirect_to(product_path(subject.id)
expect(subject).to redirect_to(product_path(response)
expect(response).to redirect_to(product_path(assign(:product))
I would appreciate any feedback, thanks in advance!
P.S.
This is rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
end
The OP posted the answer in a comment, so I'm just moving it here.
describe "creating new product" do
before :each do
#product = FactoryGirl.attributes_for(:product)
end
it "increases Product count number by one" do
expect(post :create, product: #product).to change(Product,:count).by(1)
end
it "redirects to the created product's page" do
expect(post :create, product: #product).to redirect_to product_path(assigns(:product))
end
end

RSPEC fails controller test (transaction) but works in the browser (rails 4)

I am having a strange issue with RSPEC that I am unable to nail down.
The test fails but when I try in the browser the behavior works as expected.
Here is the model code so far:
class QueueItem < ActiveRecord::Base
belongs_to :video
belongs_to :user
validates_presence_of :user_id, :video_id
validates_uniqueness_of :video_id, scope: :user_id
validates_numericality_of :position, only_integer: true
delegate :category, to: :video
delegate :title, to: :video, prefix: :video
...
end
And here is the controller code so far:
class QueueItemsController < ApplicationController
...
before_action :require_user
def update_queue
begin
ActiveRecord::Base.transaction do
queue_items_params.each do |queue_item_input|
queue_item = QueueItem.find(queue_item_input[:id])
queue_item.update!(position: queue_item_input[:position])
end
end
rescue ActiveRecord::RecordInvalid
flash[:danger] = "Your queue was not updated, make sure you only use numbers to set the position"
redirect_to queue_items_path
return
end
current_user.queue_items.each_with_index { |queue_item, i| queue_item.update(position: i+1 )}
redirect_to queue_items_path
end
private
def queue_items_params
params.permit(queue_items:[:id, :position])[:queue_items]
end
...
end
And now the controller spec that fails:
describe "POST #update" do
context 'when user is signed in' do
let(:user) { Fabricate(:user) }
before { session[:user] = user.id }
context 'with invalid attributes' do
it "does not update the queue items position" do
queue_item1 = Fabricate(:queue_item, user: user, position: 1)
queue_item2 = Fabricate(:queue_item, user: user, position: 2)
post :update_queue, queue_items: [{id: queue_item1.id, position: 6}, {id: queue_item2.id, position: 2.2}]
expect(queue_item1.reload.position).to eq(1)
expect(queue_item2.reload.position).to eq(2)
end
end
end
end
And the error message:
Failures:
1) QueueItemsController POST #update when user is signed in with invalid attributes does not update the queue items position
Failure/Error: expect(queue_item1.reload.position).to eq(1)
expected: 1
got: 6
(compared using ==)
I don't understand why the spec fails but in the browser it works.
What am I missing?
Thank you very much
I actually found the issue!
It was due to DatabaseCleaner gem which I am using in my rspec_helper.rb setup.
The following setup did not work:
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
I fixed it by changing it to:
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :deletion
end

Rails & RSpec - Testing Concerns class methods

I have the following (simplified) Rails Concern:
module HasTerms
extend ActiveSupport::Concern
module ClassMethods
def optional_agreement
# Attributes
#----------------------------------------------------------------------------
attr_accessible :agrees_to_terms
end
def required_agreement
# Attributes
#----------------------------------------------------------------------------
attr_accessible :agrees_to_terms
# Validations
#----------------------------------------------------------------------------
validates :agrees_to_terms, :acceptance => true, :allow_nil => :false, :on => :create
end
end
end
I can't figure out a good way to test this module in RSpec however - if I just create a dummy class, I get active record errors when I try to check that the validations are working. Has anyone else faced this problem?
Check out RSpec shared examples.
This way you can write the following:
# spec/support/has_terms_tests.rb
shared_examples "has terms" do
# Your tests here
end
# spec/wherever/has_terms_spec.rb
module TestTemps
class HasTermsDouble
include ActiveModel::Validations
include HasTerms
end
end
describe HasTerms do
context "when included in a class" do
subject(:with_terms) { TestTemps::HasTermsDouble.new }
it_behaves_like "has terms"
end
end
# spec/model/contract_spec.rb
describe Contract do
it_behaves_like "has terms"
end
You could just test the module implicitly by leaving your tests in the classes that include this module. Alternatively, you can include other requisite modules in your dummy class. For instance, the validates methods in AR models are provided by ActiveModel::Validations. So, for your tests:
class DummyClass
include ActiveModel::Validations
include HasTerms
end
There may be other modules you need to bring in based on dependencies you implicitly rely on in your HasTerms module.
I was struggling with this myself and conjured up the following solution, which is much like rossta's idea but uses an anonymous class instead:
it 'validates terms' do
dummy_class = Class.new do
include ActiveModel::Validations
include HasTerms
attr_accessor :agrees_to_terms
def self.model_name
ActiveModel::Name.new(self, nil, "dummy")
end
end
dummy = dummy_class.new
dummy.should_not be_valid
end
Here is another example (using Factorygirl's "create" method" and shared_examples_for)
concern spec
#spec/support/concerns/commentable_spec
require 'spec_helper'
shared_examples_for 'commentable' do
let (:model) { create ( described_class.to_s.underscore ) }
let (:user) { create (:user) }
it 'has comments' do
expect { model.comments }.to_not raise_error
end
it 'comment method returns Comment object as association' do
model.comment(user, "description")
expect(model.comments.length).to eq(1)
end
it 'user can make multiple comments' do
model.comment(user, "description")
model.comment(user, "description")
expect(model.comments.length).to eq(2)
end
end
commentable concern
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
def comment(user, description)
Comment.create(commentable_id: self.id,
commentable_type: self.class.name,
user_id: user.id,
description: description
)
end
end
and restraunt_spec may look something like this (I'm not Rspec guru so don't think that my way of writing specs is good - the most important thing is at the beginning):
require 'rails_helper'
RSpec.describe Restraunt, type: :model do
it_behaves_like 'commentable'
describe 'with valid data' do
let (:restraunt) { create(:restraunt) }
it 'has valid factory' do
expect(restraunt).to be_valid
end
it 'has many comments' do
expect { restraunt.comments }.to_not raise_error
end
end
describe 'with invalid data' do
it 'is invalid without a name' do
restraunt = build(:restraunt, name: nil)
restraunt.save
expect(restraunt.errors[:name].length).to eq(1)
end
it 'is invalid without description' do
restraunt = build(:restraunt, description: nil)
restraunt.save
expect(restraunt.errors[:description].length).to eq(1)
end
it 'is invalid without location' do
restraunt = build(:restraunt, location: nil)
restraunt.save
expect(restraunt.errors[:location].length).to eq(1)
end
it 'does not allow duplicated name' do
restraunt = create(:restraunt, name: 'test_name')
restraunt2 = build(:restraunt, name: 'test_name')
restraunt2.save
expect(restraunt2.errors[:name].length).to eq(1)
end
end
end
Building on Aaron K's excellent answer here, there are some nice tricks you can use with described_class that RSpec provides to make your methods ubiquitous and make factories work for you. Here's a snippet of a shared example I recently made for an application:
shared_examples 'token authenticatable' do
describe '.find_by_authentication_token' do
context 'valid token' do
it 'finds correct user' do
class_symbol = described_class.name.underscore
item = create(class_symbol, :authentication_token)
create(class_symbol, :authentication_token)
item_found = described_class.find_by_authentication_token(
item.authentication_token
)
expect(item_found).to eq item
end
end
context 'nil token' do
it 'returns nil' do
class_symbol = described_class.name.underscore
create(class_symbol)
item_found = described_class.find_by_authentication_token(nil)
expect(item_found).to be_nil
end
end
end
end

Is there a way to prevent having to specify associations in tests?

Given I have the following class
class listing > ActiveRecord::Base
attr_accessible :address
belongs_to :owner
validates :owner_id, presence: true
validates :address, presence: true
end
Is there a way I can get away with not having to keep associating an owner before I save a listing in my tests in /spec/models/listing_spec.rb, without making owner_id accessible through mass assignment?
describe Listing do
before(:each) do
#owner = Factory :owner
#valid_attr = {
address: 'An address',
}
end
it "should create a new instance given valid attributes" do
listing = Listing.new #valid_attr
listing.owner = #owner
listing.save!
end
it "should require an address" do
listing = Listing.new #valid_attr.merge(:address => "")
listing.owner = #owner
listing.should_not be_valid
end
end
No need to use factory-girl (unless you want to...):
let(:valid_attributes) { address: 'An Address', owner_id: 5}
it "creates a new instance with valid attributes" do
listing = Listing.new(valid_attributes)
listing.should be_valid
end
it "requires an address" do
listing = Listing.new(valid_attributes.except(:address))
listing.should_not be_valid
listing.errors(:address).should include("must be present")
end
it "requires an owner_id" do
listing = Listing.new(valid_attributes.except(:owner_id))
listing.should_not be_valid
listing.errors(:owner_id).should include("must be present")
end
There is if you use factory-girl
# it's probably not a good idea to use FG in the first one
it "should create a new instance given valid attributes" do
listing = Listing.new #valid_attr
listing.owner = #owner
listing.property_type = Factory(:property_type)
listing.save!
end
it "should require an address" do
# But here you can use it fine
listing = Factory.build :listing, address: ''
listing.should_not be_valid
end
it "should require a reasonable short address" do
listing = Factory.build :listing, address: 'a'*245
listing.should_not be_valid
end
I hate to be the voice of dissent here, but you shouldn't be calling save! or valid? at all in your validation spec. And 9 times out of 10, if you need to use factory girl just to check the validity of your model, something is very wrong. What you should be doing is checking for errors on each of the attributes.
A better way to write the above would be:
describe Listing do
describe "when first created" do
it { should have(1).error_on(:address) }
it { should have(1).error_on(:owner_id) }
end
end
Also, chances are you don't want to be checking for the presence of an address, you want to check that it is not nil, not an empty string, and that it is not longer than a certain length. You'll want to use validates_length_of for that.

Resources