Currently learning ROR via The Odin Project Curriculum. As part of the 'PROJECT: Building with active record' I have set up three basic models: User, Post, Comments.
In both Post and Comments my basic 'Post/Comments is valid' test is failing(false expecting truthy), but all specific tests pass, e.g. code for PostTest:
require 'test_helper'
class PostTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
def setup
#post = Post.new(title: "Hello World", body: "Hello world, it's nice to
meet you", user_id: 1)
end
test "Post is valid" do
assert #post.valid?
end
test "Post title is not blank" do
#post.title = ""
assert_not #post.valid?
end
test "Post title is less than 40 char" do
#post.title = "a"*51
assert_not #post.valid?
end
test "Post body is not blank" do
#post.body = ""
assert_not #post.valid?
end
test "Post has user_id" do
#post.user_id = ""
assert_not #post.valid?
end
end
The reason I am confused is that if I use rails console to manually create the same Post as in the #setup above then the valid? method correctly returns true. Additionally the User model setup with exactly the same formation of #setup and user is valid tests passes fine. The only difference I see is that User has_many :posts where as the Posts belong_to :user
For ref the Post model is:
class Post < ApplicationRecord
validates :title, presence: true, length: {maximum: 40}
validates :body, presence: true
validates :user_id, presence: true
belongs_to :user
has_many :comments
end
Are you using fixtures for your users or how do you know a user record with id 1 exists?
if you are using fixtures, you should create a user in your setup:
def setup
user = users(:default_user)
#post = Post.new(
title: "Hello World",
body: "Hello world, it's nice to meet you",
user: user
)
end
See more on fixtures: http://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures
Related
I am starting to get into testing with minitest in Rails. Currently my first test to see if my model is valid is returning false. The error message itself is also very generic Expected false to be truthy. I have tested everything else and all of those tests work fine. Does anybody know what could be causing this?
article_test.rb
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
def setup
#article = Article.new(title:"Avengers Endgame", body:"I am inevitable")
end
test "article should be valid" do
assert #article.valid?
end
test "title should be present" do
#article.title = " "
assert_not #article.valid?
end
test "body should not be too short" do
#article.body = "aa"
assert_not #article.valid?
end
end
article.rb
class Article < ApplicationRecord
include Visible
belongs_to :user
has_many :comments, dependent: :destroy
has_rich_text :body
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
The main problem here is that you're using a poor method for testing your validations.
assert #article.valid? and assert_not #article.valid? does not actually tell you anything at all about the object under test. When the test fails you're none the wiser about why it actually failed and if the failure is actually even connected to what you're proportedly testing. At best it serves as a sort of litmus test that your test setup is correct.
Instead of this "carpet bombing" approach test each validation on its own:
class ArticleTest < ActiveSupport::TestCas
test "title should be present" do
article = Article.new(title: '')
article.valid?
assert_includes build_article.errors[:title], "can’t be blank"
end
test "body should not be too short" do
article = Article.new(body: 'aa')
article.valid?
assert_includes article.errors[:body], "is too short"
end
end
Testing all the validations at once (creating a record with valid input) will be covered by your integration and system tests anyways.
You have belongs_to :user, which expects the #article to have a user_id compulsorily to be present, before it can be saved.
If user_id is optional during creation, change this:
belongs_to :user
to
belongs_to :user, optional: true
I have a customer model that belongs to user, and my controller test for post#create succeeds. But I have a subscription model that belongs to both user and plan, and it is failing (I'm using rails 5.1.2).
Here's my spec:
#rspec/controllers/checkout/subscriptions_controller_spec.rb
require 'rails_helper'
RSpec.describe Checkout::SubscriptionsController, type: :controller do
describe 'POST #create' do
let!(:user) { FactoryGirl.create(:user) }
before do
sign_in user
end
context 'with valid attributes' do
it 'creates a new subscription' do
expect { post :create, params: { subscription: FactoryGirl.attributes_for(:subscription) } }.to change(Subscription, :count).by(1)
end
end
end
end
Subscription controller:
# app/controllers/checkout/subscriptions_controller.rb
module Checkout
class SubscriptionsController < Checkout::CheckoutController
before_action :set_subscription, only: %i[edit update destroy]
before_action :set_options
def create
#subscription = Subscription.new(subscription_params)
#subscription.user_id = current_user.id
if #subscription.valid?
respond_to do |format|
if #subscription.save
# some code, excluded for brevity
end
end
else
respond_to do |format|
format.html { render :new }
format.json { render json: #subscription.errors, status: :unprocessable_entity }
end
end
end
private
def set_subscription
#subscription = Subscription.find(params[:id])
end
def set_options
#categories = Category.where(active: true)
#plans = Plan.where(active: true)
end
def subscription_params
params.require(:subscription).permit(:user_id, :plan_id, :first_name, :last_name, :address, :address_2, :city, :state, :postal_code, :email, :price)
end
end
end
Subscription model -
# app/models/subscription.rb
class Subscription < ApplicationRecord
belongs_to :user
belongs_to :plan
has_many :shipments
validates :first_name, :last_name, :address, :city, :state, :postal_code, :plan_id, presence: true
before_create :set_price
before_update :set_price
before_create :set_dates
before_update :set_dates
def set_dates
# some code, excluded for brevity
end
def set_price
# some code, excluded for brevity
end
end
I'm also using some FactoryGirl factories for my models.
# spec/factories/subscriptions.rb
FactoryGirl.define do
factory :subscription do
first_name Faker::Name.first_name
last_name Faker::Name.last_name
address Faker::Address.street_address
city Faker::Address.city
state Faker::Address.state_abbr
postal_code Faker::Address.zip
plan
user
end
end
# spec/factories/plans.rb
FactoryGirl.define do
factory :plan do
name 'Nine Month Plan'
description 'Nine Month Plan description'
price 225.00
active true
starts_on Date.new(2017, 9, 1)
expires_on Date.new(2018, 5, 15)
monthly_duration 9
prep_days_required 5
category
end
end
# spec/factories/user.rb
FactoryGirl.define do
factory :user do
name Faker::Name.name
email Faker::Internet.email
password 'Abcdef10'
end
end
When I look at the log, I notice that user and plan aren't being populated when running the spec and creating the subscription, which must be why it's failing, since plan is required. But I can't figure out how to fix this. Any ideas? Thanks in advance.
The issue is that, by your model definition, you can only create a Subscription that is associated to an existing Plan:
class Subscription < ApplicationRecord
belongs_to :plan
validates :plan_id, presence: true
end
You could have debugged this issue by either setting a breakpoint in the rspec test and inspecting the response.body; or similarly instead by setting a breakpoint in SubscriptionsController#create and inspecting #subscription.errors. Either way, you should see the error that plan_id is not present (so therefore the #subscription did not save).
The issue stems from the fact that FactoryGirl#attributes_for does not include associated model IDs. (This issue has actually been raised many times in the project, and discussed at length.)
You could just explicitly pass a plan_id in the request payload of your test, to make it pass:
it 'creates a new subscription' do
expect do
post(
:create,
params: {
subscription: FactoryGirl.attributes_for(:subscription).merge(post_id: 123)
}
end.to change(Subscription, :count).by(1)
end
However, this solution is somewhat arduous and error prone. A more generic alternative I would suggest is define the following spec helper method:
def build_attributes(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].include?(k)
end
end
This utilises the fact that build(:subscription).attributes does include foreign keys, as it references the associations.
You could then write the test as follows:
it 'creates a new subscription' do
expect do
post(
:create,
params: {
subscription: build_attributes(:subscription)
}
)
end.to change(Subscription, :count).by(1)
end
Note that this test is still slightly unrealistic, since the Post does not actually exist in the database! For now, this may be fine. But in the future, you may find that the SubscriptionController#create action actually needs to look up the associated Post as part of the logic.
In this case, you'd need to explicitly create the Post in your test:
let!(:post) { create :post }
let(:subscription) { build :subscription, post: post }
...And then send the subscription.attributes to the controller.
I'm super new to testing my app using RSpec and I'm trying to test the validation of a comment without a user and keep getting syntax errors.
Here is the comment model code.
class Comment < ApplicationRecord
belongs_to :user
belongs_to :product
scope :rating_desc, -> { order(rating: :desc) }
validates :body, presence: true
validates :user, presence: true
validates :product, presence: true
validates :rating, numericality: { only_integer: true }
after_create_commit { CommentUpdateJob.perform_later(self, user) }
end
and here is the comment spec:
require 'rails_helper'
describe Comment do
before do
#product = Product.create!(name: "race bike", description: "fast race bike")
#user = User.create!(email: "jerryhoglen#me.com", password: "Maggie1!")
#product.comments.create!(rating: 1, user: #user, body: "Awful bike!")
end
it "is invalid without a user"
expect(build(:comment, user:nil)).to_not be_valid
end
end
What you're doing here is good - building objects and using the be_valid matcher. But if you use shoulda-matchers there's a one-liner to test a model validation:
describe Comment do
it { is_expected.to validate_presence_of :user }
end
You can do this for other validations such as uniqueness, numericality, etc, though you'd have to look up the syntax.
you missed a do, do it like:
it "is invalid without a user" do
expect(build(:comment, user: nil)).to_not be_valid
end
But that's not a very clear test when it fails, I suggest you check the actual expected validation error.
That's what it may look like:
expect(ValidatingWidget.new.errors_on(:name)).to include("can't be blank")
expect(ValidatingWidget.new(:name => "liquid nitrogen")).to have(0).errors_on(:name)
See rspec-rails errors_on # relishapp
I'm trying to create a model method that counts the number of posts for a user, and then test it with Rspec.
But I'm running into an error,
undefined method `count_posts' for #<User:0x000000044d42a8>
User Model
has_many :posts
def self.count_posts
self.posts.all.count
end
Posts Model
belongs_to :user
User_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe "count_posts" do
before do
#user1 = create(:user)
post = create(:post, user: #user1)
end
it "Returns number of posts for a user" do
expect( #user1.count_posts ).to eq(1)
end
end
end
/factories/users.rb
FactoryGirl.define do
factory :user do
sequence(:email, 100) { |n| "person#{n}#example.com"}
password "helloworld"
password_confirmation "helloworld"
end
end
/factories/posts.rb
FactoryGirl.define do
factory :post do
title "Post Title"
body "Post bodies must be pretty long."
user
end
end
I don't understand why its an undefined method, unless I've written it incorrectly in the model (which I fully accept as possible).
Apologies in advance if this question is too newbish. But I haven't fully grasped Rspec testing or the use of self.
According to your logic, count_posts must be an instance method instead of class method:
class User < ActiveRecord::Base
has_many :posts
def count_posts
posts.count # or posts.size
end
end
I'm starting out with Rails 4.2, and I'm try to test uniqueness for the Item models I'm making, I ran this code:
item.rb:
class Item < ActiveRecord::Base
attr_accessor :name
validates :name, uniqueness: true #, other validations...
end
item_test.rb:
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
#item = Item.new(name: "Example Item")
end
test "name should be unique" do
duplicate_item = #item.dup
#item.save
assert_not duplicate_item.valid?
end
end
but the test didn't pass, saying that the assert_not line is coming out true when it should be nil or false. I basically got this code from a tutorial but cannot figure out why it's not passing. Any help?
Edit: I found the solution, by not defining the other members (specifically :price ) of #item that I defined in the setup action, the test passed. However now I don't know how to make it pass with the the :price member. Below is the full implementation of item.rb & item_test.rb.
item.rb:
class Item < ActiveRecord::Base
attr_accessor :name, :description, :price
validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
validates :description, presence: true,
length: { maximum: 1000 }
VALID_PRICE_REGEX = /\A\d+(?:\.\d{0,2})?\z/
validates :price, presence: true,
:format => { with: VALID_PRICE_REGEX },
:numericality => {:greater_than => 0}
end
item_test.rb:
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
#item = Item.new(name: "Example Item", description: "Some kind of item.", price: 1.00)
end
test "name should be unique" do
duplicate_item = #item.dup
#item.save
assert_not duplicate_item.valid?
end
end
Almaron's answer above is correct and should be the accepted answer.
I am adding this answer to elaborate on it.
The test would be as follows:
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
#item = Item.create(name: "Example Item")
end
test "name should be unique" do
duplicate_item = #item.dup
assert_not duplicate_item.valid?
end
end
Note: duplicate_item need not be saved before validating it.
The uniqueness validation is performed against the records already existing in the database. And your Item.new(name: "Example Item") is not in the database untill it is saved. So if you use Item.create(name: "Example Item") instead, the test should pass.
You've identified at least some of the problem in your edit.
The problem isn't that you're using Item.new instead of Item.create the problem is that when you do #item.save the #item record isn't being saved because it has other validation issues.
You could try...
#item.save(validate: false)
... which will force #item to be written to the database, but the test doesn't really determine why the duplicate_item record is invalid.
Better might be to test that you have an error relating to name...
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
#item = Item.new(name: "Example Item")
end
test "name should be unique" do
duplicate_item = #item.dup
#item.save(validate: false)
duplicate_item.valid? # need this to populate errors
assert duplicate_item.errors
assert duplicate_item.errors[:name]
end
end
I fixed it, I got rid of the attr_accessor line, the test was then able to access the attributes and was able to detect the duplication.