I was trying to practice Rspec, but seems I was confused about some rails part.
Here is Zombie.rb
class Zombie < ActiveRecord::Base
validates :name, presence: true
has_many :tweets
def hungry?
true
end
end
It seems when I create a Zombie instance, it will check the name attribute.
So, I wrote these Rspec code.
it 'should not be hungry after eating' do
#zombie = Zombie.create
#zombie.should_not be_valid
#zombie.hungry?.should be_truthy
end
Why it will pass? If the #zombie is not valid, why #zombie.hungry? will still return true
hungry? will always return true, therefore your expectation always passes.
That your instance is invalid doesn't mean that the instance is not valid Ruby. It is still a fully functional instance. The valid? method returns false, because the values of this instance are not valid to you, since you defined that it is not valid to you without a name.
From Ruby's point of view it is still a valid Zombie instance.
Btw since you just started to learn RSpec. You use the old RSpec syntax, you might want to learn the new syntax instead which would look like this:
describe Zombie do
context 'without a name' do
subject(:zombie) { Zombie.new }
it 'is not valid' do
expect(zombie).to_not be_valid
end
it 'is hungry' do
expect(zombie).to be_hungry
end
end
end
because your hungry? method always returns true
be_truthy # passes if obj is truthy (not nil or false) even if your
object is nil it's returns true
be_truthy
z = Zombie.create - It will not be created because of validation
z.hungry? => true
so your test passed
Related
Sorry for the vague title, there are a lot of moving parts to this problem so I think it will only be clear after seeing my code. I'm fairly sure I know what's going on here and am looking for feedback on how to do it differently:
I have a User model that sets a uuid attr via an ActiveRecord callback (this is actually in a "SetsUuid" concern, but the effect is this):
class User < ActiveRecord::Base
before_validation :set_uuid, on: :create
validates :uuid, presence: true, uniqueness: true
private
def set_uuid
self.uuid = SecureRandom.uuid
end
end
I am writing a functional rspec controller test for a "foo/add_user" endpoint. The controller code looks like this (there's some other stuff like error-handling and #foo and #params being set by filters, but you get the point. I know this is all working.)
class FoosController < ApplicationController
def add_user
#foo.users << User.find_by_uuid!(#params[:user_id])
render json: {
status: 'awesome controller great job'
}
end
end
I am writing a functional rspec controller test for the case "foo/add_user adds user to foo". My test looks roughly this (again, leaving stuff out here, but the point should be obvious, and I know it's all working as intended. Also, just to preempt the comments: no, I'm not actually 'hardcoding' the "user-uuid" string value in the test, this is just for the example)
RSpec.describe FoosController, type: :controller do
describe '#add_user' do
it_behaves_like 'has #foo' do
it_behaves_like 'has #params', {user_id: 'user-uuid'} do
context 'user with uuid exists' do
let(:user) { create(:user_with_uuid, uuid: params[:user_id]) } # params is set by the 'has #params' shared_context
it 'adds user with uuid to #foo' do
route.call() # route is defined by a previous let that I truncated from this example code
expect(foo.users).to include(user) # foo is set by the 'has #foo' shared_context
end
end
end
end
end
end
And here is my user factory (I've tried setting the uuid in several different ways, but my problem (that I go into below) is always the same. I think this way (with traits) is the most elegant, so that's what I'm putting here):
FactoryGirl.define do
factory :user do
email { |n| "user-#{n}#example.com" }
first_name 'john'
last_name 'naglick'
phone '718-555-1234'
trait :with_uuid do
after(:create) do |user, eval|
user.update!(uuid: eval.uuid)
end
end
factory :user_with_uuid, traits: [:with_uuid]
end
end
Finally, The problem:
This only works if I reference user.uuid before route.call() in the spec.
As in, if I simply add the line "user.uuid" before route.call(), everything works as intended.
If I don't have that line, the spec fails because the user's uuid doesn't actually get updated by the after(:create) callback in the trait in the factory, and thus the User.find_by_uuid! line in the controller does not find the user.
And just to preempt another comment: I'm NOT asking how to re-write this spec so that it works like I want. I already know a myriad of ways to do this (the easiest and most obvious being to manually update user.uuid in the spec itself and forget about setting the uuid in the factory altogether). The thing I'm asking here is why is factorygirl behaving like this?
I know it has something to do with lazy-attributes (obvious by the fact it magically works if I have a line evaluating user.uuid), but why? And, even better: is there some way I can do what I want here (setting the uuid in the factory) and have everything work like I intend? I think it's a rather elegant looking use of rspec/factorygirl, so I'd really like it to work like this.
Thanks for reading my long question! Very much appreciate any insight
Your issue has less to do with FactoryGirl and more to do with let being lazily evaluated.
From the docs:
Use let to define a memoized helper method. The value will be cached across
multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method's
invocation before each example.
Since your test doesn't invoke the user object until the expectation there is nothing created. To force rspec to load object, you can use let!.
Instead of using the before_validation callback you should be using after_initialize. That way the callback is fired even before .valid? is called in the model lifecycle.
class User < ActiveRecord::Base
before_initialization :set_uuid!, on: :create, if: :set_uuid?
validates :uuid, presence: true, uniqueness: true
private
def set_uuid!
# we should also check that the UUID
# does not actually previously exist in the DB
begin
self.uuid = SecureRandom.uuid
end while User.where(uuid: self.uuid).any?
end
def set_uuid?
self.uuid.nil?
end
end
Although the chance of generating the same hash twice with SecureRandom.uuid is extremely slim it is possible due to the pigeonhole principle. If you maxed out in the bad luck lottery this would simply generate a new UUID.
Since the callback fires before validation occurs the actual logic here should be completely self contained in the model. Therefore there is no need to setup a callback in FactoryGirl.
Instead you would setup your spec like so:
let!(:user) { create(:user) }
it 'adds user with uuid to #foo' do
post :add_user, user_id: user.uuid, { baz: 3 }
end
I'm using the gem Frozen Record in order to setup a series of invariable questions in my Rails app, which will be accessed and used by other models. Given I'm used to Test Driven Development, I'd already set up a series of tests and validations before adding the Frozen Record gem:
class Question < FrozenRecord::Base
validates :next_question_id_yes, :question_text, :answer_type, presence: true
end
And the tests:
require 'rails_helper'
RSpec.describe Question, :type => :model do
let(:question) {Question.new(id: "1", question_text: "who's the daddy?", answer_type: 'Boolean', next_question_id_yes: '7', next_question_id_no: "9")}
it 'is valid' do
expect(question).to be_valid
end
#questions can be valid without a next_question_id_no, because some questions just capture info
#questions must have a next_question_id_yes as that will route them to the subsequent question
#if it's the last question in a block, we will use the next number up to signify the questions are complete
it 'is invalid without a next_question_id_yes' do
question.next_question_id_yes = nil
expect(question).to_not be_valid
end
it 'is invalid without a question_text' do
question.question_text = nil
expect(question).to_not be_valid
end
it 'is invalid without an answer_type' do
question.answer_type = nil
expect(question).to_not be_valid
end
end
All of these passed when I had the class Question < ActiveRecord::Base but since becoming a FrozenRecord I get:
rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/frozen_record-0.4.0/lib/frozen_record/base.rb:78:in `method_missing': undefined method `validates' for Question:Class (NoMethodError)
I presume there's no method in Frozen Record to validate the presence of these columns. What I'm less sure of is:
Do I need to perform validations on static data loaded via YAML? My first feeling was that this would be an additional check on my YAML code.
If I do, can I write custom validations to check this data?
Has anyone else used this gem and overcome the issue using Rspec? (it's been downloaded thousands of times according to RubyGems)
Any help would be appreciated.
Sorry to waste everyone's time - I just looked through the source code of FrozenRecord and worked out I can do it like this:
class Question < FrozenRecord::Base
include ActiveModel::Validations
validates :next_question_id_yes, :question_text, :answer_type, presence: true
self.base_path = '/config/initializers/questions.yml'
end
Should think before I ask next time.
I'm getting intermittent test failures when using instance_double.
I have a file with 4 specs in it. Here is the source:
require 'rails_helper'
describe SubmitPost do
before(:each) do
#post = instance_double('Post')
allow(#post).to receive(:submitted_at=)
end
context 'on success' do
before(:each) do
allow(#post).to receive(:save).and_return(true)
#result = SubmitPost.call(post: #post)
end
it 'should set the submitted_at date' do
expect(#post).to have_received(:submitted_at=)
end
it 'should call save' do
expect(#post).to have_received(:save)
end
it 'should return success' do
expect(#result.success?).to eq(true)
expect(#result.failure?).to eq(false)
end
end
context 'on failure' do
before(:each) do
allow(#post).to receive(:save).and_return(false)
#result = SubmitPost.call(post: #post)
end
it 'should return failure' do
expect(#result.success?).to eq(false)
expect(#result.failure?).to eq(true)
end
end
end
This is a Rails 4.1.4 application. Internally, SubmitPost sets submitted_at and calls save on the passed-in Post. My Post model looks like this:
class Post < ActiveRecord::Base
validates :title, presence: true
validates :summary, presence: true
validates :url, presence: true
validates :submitted_at, presence: true
scope :chronological, -> { order('submitted_at desc') }
end
It's super vanilla.
When I run rake, rspec, or bin/rspec, I get all all four tests failing 20% - 30% of the time. The error message is always:
Failure/Error: allow(#post).to receive(:submitted_at=)
Post does not implement: submitted_at=
If I label one of the specs with focus: true, that one spec will fail 100% of the time.
If I replace instance_double with double, all specs will succeed 100% of the time.
It appears that instance_double is having some difficulty inferring the methods available on the Post class. It also appears to be somewhat random and timing-based.
Has anyone run into this issue? Any ideas what might be wrong? Any sense of how to go about troubleshooting this? Naturally, inserting a debugging breakpoint causes the specs to pass 100% of the time.
The problem you are seeing is that ActiveRecord creates column methods dynamically. instance_double uses 'Post' to look up methods to verify you are stubbing them correctly (unless the class doesn't exist yet or has not been loaded).
When a prior spec loads the model, ActiveRecord will create those dynamic methods so your spec passes as RSpec can then find the methods (with a respond_to? call). When run in isolation the model hasn't been previously used and so ActiveRecord will not have created the dynamic methods yet and your test fails as you're experiencing.
The workaround for this is to force ActiveRecord to load the dynamic methods when they are called in your spec:
class Post < ActiveRecord::Base
def submitted_at=(value)
super
end
end
See the RSpec documentation for further explanation and workarounds for the problem:
https://www.relishapp.com/rspec/rspec-mocks/docs/verifying-doubles/dynamic-classes
I'm using ruby 1.9.2 and rails 3.2.2.
I have a 'domain' model (domain.rb):
class Domain < ActiveRecord::Base
attr_accessible :url
belongs_to :user
VALID_DOMAIN_REGEX = /^[a-z0-9\-\.]+\.[a-z]{2,}$/i
validates :url, presence:true,
format: { with: VALID_DOMAIN_REGEX },
uniqueness: { case_sensitive: false }
end
And a test asserting that a duplicate domain should not be valid:
require 'spec_helper'
describe Domain do
before do
#domain = FactoryGirl.create(:domain)
end
subject { #domain }
describe "when domain url is already taken" do
before do
domain_with_same_url = #domain.dup
domain_with_same_url.url = #domain.url.upcase
domain_with_same_url.save
end
it { should_not be_valid }
end
end
The test keeps failing:
1) Domain when domain url is already taken
Failure/Error: it { should_not be_valid }
expected valid? to return false, got true
# ./spec/models/domain_spec.rb:31:in `block (3 levels) in '
#domain is already created, validated and saved.
The domain_with_same_url is the new record, and it should be invalid. But you are not checking it.
Try
domain_with_same_url = FactoryGirl.create(:domain, :url => #domain.url.upcase)
domain_with_same_url.should_not be_valid
Your two before blocks are run in outer-to inner order. Thus, when running your testsuite, first your #domain object gets created and saved, then the inner before block gets executed. Hiwever, your domain_with_same_url probably gets never actually saved because it's validation fails which probably leats to domain_with_same_url.save to return false.
As a workaround, you could check the validity of domain_with_same_url instead of #domain.
Seems like your subject of your test case is #domain, which is a valid object. Whether use new subject for #domain_with_same_url (don't forget to make it an instance variable), or explicitly say domain_with_same_url.should ... (just like The Who indicated in his answer).
I just had the same problem in Rails 4 Model column uniqueness not working. It provides two other solutions without a second call to FactoryGirl, just FYI. The one I am using is:
before(:each) do
#feed_with_same_url = #feed.dup
#feed_with_same_url.feed_url = #feed.feed_url
end
it { #feed_with_same_url.should_not be_valid }
The save is causing some problem, unexpected to me. And, you need to reference an object to should_not_be_valid as a local variable.
I'm trying to write up a rails gem that involves (amongst other things) some custom model validators...and I'm wondering how to test validation options.
To give an example, I'd like to write an rspec test for which a blank field returns valid if the allow_nil option is true, and invalid otherwise. The code works fine, but I can't think of an elegant way to test it. The code itself:
Module ActiveModel
module Validations
module ThirstyVals
class ValidatePrime < EachValidator
# Validate prime numbers
def validate_each(record, attr_name, value)
return if options[:allow_nil] && value.strip.length == 0
# Other validation code here
# ...
end
end
end
end
end
I'm currently testing through a dummy project, which is fine, but the only way I can think of to test the :allow_nil option is to write up a new attribute with :allow_nil set, and verify its functionality...which seems both excessive and pretty inelegant. There must be a more graceful way - any ideas appreciated. (Other tests below for posterity)
# ...
before(:each) do
#entry = Entry.new
end
describe "it should accept valid prime numbers" do
['7', '13', '29'].each do |n|
#entry.ticket = n
#entry.valid?('ticket').should be_true
end
end
describe "it should reject non-prime numbers" do
['4', '16', '33'].each do |n|
#entry.ticket = n
#entry.valid?('ticket').should be_false
end
end
have you considered testing the validator in isolation like so:
in validate_prime_spec.rb
require path_to_validator_file
describe ActiveModel::Validations::ThirstyVals::ValidatePrime do
context :validate_each do
it 'should do some stuff' do
subject.validate_each(record, attr_name, value).should #some expectation
end
end
end
then may I suggest that you need not test the allow_nil functionality of Rails validations due to the fact that it is already tested in Rails? (see: activemodel/test/cases/validations/inclusion_validation_test.rb line 44)