Shared example methods between rspec tests - ruby-on-rails

Every model I'm testing has the same "it must have atribute" test, testing the validates_presence_of for certain attributes. So, my goal is to create a "helper" that includes this test in a modular way.
Here is what I have:
# /spec/helpers.rb
module TestHelpers
# Runs generic validates_presence_of tests for models
def validate_presence( object, attributes=[] )
attributes.each do |attr|
it "must have a #{attr}" do
object.send("#{attr}=", nil)
expect(object).not_to be_valid
end
end
end
end
And
# /spec/rails_helper.rb
# Added
require 'helpers'
# Added
RSpec.configure do |config|
config.include TestHelpers
end
And
# /spec/models/business_spec.rb
require 'rails_helper'
RSpec.describe Business, type: :model do
describe "Validations" do
before :each do
#business = FactoryGirl.build(:business)
end
# Presence
validate_presence #business, %w(name primary_color secondary_color)
However, I'm getting the following error:
`validate_presence` is not available on an example group
I had read about shared_helpers and using it_behaves_as, but I'm not sure if that is what I'm looking for. Maybe I'm just thinking about doing this in the wrong manner.
--UPDATE--
If I place the validate_presence method into an it block, I get this error:
Failure/Error: it { validate_presence #business, %w(name primary_color secondary_color published) }
`it` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).

Shared examples are for testing the same logic across different models. Here you're just testing one model so it wouldn't apply to you. Even though I don't recommend testing core validators such as presence, here is how you would do it
# /spec/models/business_spec.rb
require 'rails_helper'
RSpec.describe Business, type: :model do
let(:business) { FactoryGirl.build(:business) }
context "when validating" do
%w(name primary_color secondary_color).each |attribute|
it "checks the presence of #{attribute} value" do
business.send("#{attribute}=", nil)
expect(business).to_not be_valid
expect(business.errors[attribute]).to be_any
end
end
end
end
Also, the validate_presence helper you're trying to use is part of the shoulda-matchers library.

Related

RSpec with FactoryGirl explicit subject

I'm using RSpec with FactoryGirl within a Ruby on Rails environment for testing.
I want to specify my factories as follows:
factory :user do
role # stub
factory :resident do
association :role, factory: :resident_role
end
factory :admin do
association :role, factory: :admin_role
end
end
And I'd like to do something like this in my spec:
require 'rails_helper'
RSpec.describe User, type: :model do
context "all users" do
# describe a user
# subject { build(:user) }
# it { is_expected.to be_something_or_do_something }
end
context "residents" do
# describe a resident
# subject { build(:resident) }
# it { is_expected.to be_something_or_do_something }
end
context "admins" do
# describe a admin
# subject { build(:admin) }
# it { is_expected.to be_something_or_do_something }
end
end
Can this be done by explicitly setting the subject? When I do, I keep getting duplicate roles errors.
If anyone has any advice or suggestion, it would be greatly appreciated!
But this causes the user_spec.rb to use the :user factory.
No, it does not. Assuming you configured FactoryGirl correctly, RSpec can use whatever factory you'd like "on demand" in any test file. Configuration-wise, in rails_helper.rb throw this in:
RSpec.configure do |config|
# ...
config.include FactoryGirl::Syntax::Methods
# ...
end
Then, in your spec file:
require 'rails_helper'
RSpec.describe User, type: :model do
context "all users" do
let(:user) { create(:user) }
it 'is a user' do
# Here `user` is going to be a user factory
expect(user.unit).not_to be_present
end
end
context "residents" do
let(:user) { create(:resident) }
it 'is a resident' do
# Here `user` is going to be a resident factory
expect(user.unit).to be_present
end
end
context "admins" do
let(:user) { create(:admin) }
it 'is an admin' do
# Here `user` is going to be an admin factory
expect(user.role).to be('admin_role')
end
end
end
In short, you can use create(<factory_name>) on any factory definition that exists in any one of these paths:
test/factories.rb
spec/factories.rb
test/factories/*.rb
spec/factories/*.rb
Note that if you haven't placed the config.include FactoryGirl::Syntax::Methods inside your RSpec.configure, you can still create any factory, by doing FactoryGirl.create(<factory_name>) instead of create(<factory_name>).
I don't think you would want to stop them from auto loading, and I'm not actually sure what your use case is for not allowing them to load?
RSpec automagically fetches the factory for a spec
Rspec loads all the factories into memory when your spec helper loads I believe. Because your using factory inheritence your just loading each of these into memory before your tests run, nothing is being called, no objects are being created or built. They are just ready to use in your tests.
Are you getting a specific error or is there some case I'm not seeing that you need?
I found the solution to my problems here: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
What I needed to use in my user factories was association :role, factory: :role, strategy: :build

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

RSpec - How to create helper method available to tests that will automatically embed "it" tests

I am new to ruby/rails/rspec etc.
Using rspec 2.13.1, I want to create a module with a method that can be called from my tests resulting to subsequent calls of the "it" method of the RSpec::Core::ExampleGroup.
My module:
require 'spec_helper'
module TestHelper
def invalid_without(symbols)
symbols = symbols.is_a?(Array) ? symbols : [symbols]
symbols.each do |symbol|
it "should not be valid without #{symbol.to_s.humanize}" do
# Gonna nullify the subject's 'symbol' attribute here
# and expect to have error on it
end
end
end
end
The code above was added to:
spec/support/test_helper.rb
and in my spec_helper.rb, in the RSpec.configure block, I added the following:
config.include TestHelper
Now, in a test, I do the following:
describe Foo
context "when invalid" do
invalid_without [:name, :surname]
end
end
Running this, I get:
undefined method `invalid_without' for #<Class:0x007fdaf1821030> (NoMethodError)
Any help appreciated..
Use shared example group.
shared_examples_for "a valid array" do |symbols|
symbols = symbols.is_a?(Array) ? symbols : [symbols]
symbols.each do |symbol|
it "should not be valid without #{symbol.to_s.humanize}" do
# Gonna nullify the subject's 'symbol' attribute here
# and expect to have error on it
end
end
end
describe Foo do
it_should_behave_like "a valid array", [:name, :surname]
end

Accessing session from a helper spec in rspec

I have a method in my ApplicationHelper that checks to see if there are any items in my basket
module ApplicationHelper
def has_basket_items?
basket = Basket.find(session[:basket_id])
basket ? !basket.basket_items.empty? : false
end
end
Here is my helper spec that I have to test this:
require 'spec_helper'
describe ApplicationHelper do
describe 'has_basket_items?' do
describe 'with no basket' do
it "should return false" do
helper.has_basket_items?.should be_false
end
end
end
end
however when I run the test i get
SystemStackError: stack level too deep
/home/user/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.8/lib/action_dispatch/testing/test_process.rb:13:
From debugging this i see that session is being accessed in ActionDispatch::TestProcess from #request.session, and #request is nil. When i access the session from my request specs #request is an instance of ActionController::TestRequest.
My question is can I access the session object from a helper spec? If I can, how? And if I cant what is the best practice to test this method?
****UPDATE****
This was down to having include ActionDispatch::TestProcess in my factories. Removing this include sorts the problem.
can I access the session object from a helper spec?
Yes.
module ApplicationHelper
def has_basket_items?
raise session.inspect
basket = Basket.find(session[:basket_id])
basket ? !basket.basket_items.empty? : false
end
end
$ rspec spec/helpers/application_helper.rb
Failure/Error: helper.has_basket_items?.should be_false
RuntimeError:
{}
The session object is there and returns an empty hash.
Try reviewing the backtrace in more detail to find the error. stack level too deep usually indicates recursion gone awry.
You are testing has_basket_items? action in ApplicationHelper, which check a specfic basket with a basket_id in the baskets table, so you should have some basket objects in your test which you can create using Factory_Girl gem.
Hers's an example :-
basket1 = Factory(:basket, :name => 'basket_1')
basket2 = Factory(:basket, :name => 'basket_2')
You can get more details on How to use factory_girl from this screen cast http://railscasts.com/episodes/158-factories-not-fixtures
It will create a Factory object in your test database. So, basically you can create some factory objects and then set a basket_id in session to check for its existence like this :
session[:basket_id] = basket1.id
So, your test should be like this :-
require 'spec_helper'
describe ApplicationHelper do
describe 'has_basket_items?' do
describe 'with no basket' do
it "should return false" do
basket1 = Factory(:basket, :name => 'basket_1')
basket2 = Factory(:basket, :name => 'basket_2')
session[:basket_id] = 1234 # a random basket_id
helper.has_basket_items?.should be_false
end
end
end
end
Alternatively, you can check for a basket_id which is being created by factory_girl to be_true by using :
session[:basket_id] = basket1.id
helper.has_basket_items?.should be_true

What is the correct way to stub template methods on all view specs using rspec-rails?

I have a number of view specs that need certain methods to be stubbed. Here's what I thought would work (in spec_helper.rb):
Spec::Runner.configure do |config|
config.before(:each, :type => :views) do
template.stub!(:request_forgery_protection_token)
template.stub!(:form_authenticity_token)
end
end
But when I run any view spec it fails with
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.template
Doing the exact same thing in the before(:each) block of each example works great.
I tried out your example and found out that in "config.before" block RSpec view example object is not yet fully initialized compared to "before" block in view spec file. Therefore in "config.before" block "template" method returns nil as template is not yet initialized. You can see it by including e.g. "puts self.inspect" in both these blocks.
In your case one workaround for achieving DRYer spec would be to define in spec_helper.rb
RSpec 2
module StubForgeryProtection
def stub_forgery_protection
view.stub(:request_forgery_protection_token)
view.stub(:form_authenticity_token)
end
end
RSpec.configure do |config|
config.include StubForgeryProtection
end
RSpec 1
module StubForgeryProtection
def stub_forgery_protection
template.stub!(:request_forgery_protection_token)
template.stub!(:form_authenticity_token)
end
end
Spec::Runner.configure do |config|
config.before(:each, :type => :views) do
extend StubForgeryProtection
end
end
and then in each before(:each) block where you want to use this stubs include
before(:each) do
# ...
stub_forgery_protection
# ...
end

Resources