Testing Custom Validate Action for Nested Attributes - ruby-on-rails-3

I've got a custom action to validate the number of child attributes. I've put this in the parent's model:
class Location < ActiveRecord::Base
has_many :blacklisted
accepts_nested_attributes_for :blacklisted, :reject_if => lambda { |a| a[:mac].blank? }, :allow_destroy => true
...
validate :check_blacklisted_clients_count
private
def check_blacklisted_clients_count
if self.blacklisted.reject(&:marked_for_destruction?).count > 25
self.errors.add :base, "No more than 25 blacklisted clients allowed per location."
end
end
That works fine when I add through the browser however I'm trying to test this with rspec and I can't get it to fail (or pass, whichever way you look at it).
it "should not allow 26 blacklisted macs", :focus => true do
loc = FactoryGirl.create(:location_full)
25.times do
loc.blacklisted.create(mac: '00:22:33:44:55:66')
end
loc.blacklisted.create(mac: '00:22:33:44:55:66')
puts loc.blacklisted.count
.........
end
(I know that doesn't actually test anything yet - I just wanted to make sure only 25 are created).
I'm assuming this is because there's no validation in the blacklisted.rb model.
How can I get rspec to test this validation?

The most straightforward approach is to write one spec that adds fewer than 25 blacklisted MACs, and another one that adds more than 25, and test that the former is valid and the latter is invalid.
Depending on how you feel about spec runtimes, that might work out just fine. If the test is too slow, you might want use stubs. For instance:
let(:location) { Location.new }
it "should be invalid with more than 25 blacklisted MACs" do
location.stub_chain(:blacklisted, :reject, :count) { 26 }
location.should be_invalid
location.errors(:base).should include("No more than 25 blacklisted clients allowed per location.")
end
Using stubs has its disadvantages--specs are likely to be more brittle, and coupled to the implementation too closely. On the other hand, if you were checking for 25,000 MACs, testing with real objects might not really be feasible.

Related

New to Rails 4 Testing - Need help getting started (rSpec and Devise)

I'm relatively new to testing and very new to Rails 4 and rSpec. I am trying to test a controller that uses Devise for authentication and I am stuck. All of the examples I can find are for Rails 3.
I'm using Rails 4.0.3, Devise 3.2.3, rSpec 2.14.1 and FactoryGirl 4.4.0.
class LessonPlansController < ApplicationController
before_action :authenticate_user!
# GET /lesson_plans
def index
#lesson_plans = current_user.lesson_plans.to_a
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_lesson_plan
#lesson_plan = LessonPlan.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def lesson_plan_params
params[:lesson_plan]
end
def lesson_plan_params
params.require(:lesson_plan).permit(:title, :synopsis)
end
end
Here are my factory definitions: (Maybe I don't need to define user_id in the lesson_plan factory?)
FactoryGirl.define do
factory :user do
sequence( :username ) { |n| "user#{n}" }
sequence( :email ) { |n| "foo#{n}#example.com" }
password 'foobarbaz'
password_confirmation 'foobarbaz'
created_at Time.now
updated_at Time.now
end
end
FactoryGirl.define do
factory :lesson_plan do
user_id 1
title "The French Revolution"
synopsis "Background and events leading up to the French Revolution"
end
end
And the test part is where I get stuck.
describe LessonPlansController do
let(:valid_attributes) { { } }
let(:valid_session) { {} }
# describe "GET index" do
it "assigns all lesson_plans as #lesson_plans" do
user=FactoryGirl.create(:user)
sign_in user
lesson_plan = LessonPlan.create! valid_attributes
get :index, {}, valid_session
assigns(:lesson_plans).should eq([lesson_plan])
end
end
I'm not sure what to put in valid_attributes and valid_session (or if I even need them). The test will get as far as signing in the user, but will fail on creation of the lesson_plan. Admittedly this is the default/generated test for rSpec, but I am not sure how to proceed.
Examples I have seen use a before block to set up the user. I haven't been able to find anything on the Devise wiki page covering how to write basic rSpec tests for a controller that requires the user to be logged in. Any pointers would be greatly appreciated!
"I'm not sure what to put in valid_attributes and valid_session (or if I even need them)."
Well that depends what you're testing for.. Say you're testing validations & want to ensure that a record not be created if x column is set to null... then you could try to specifically create a record with invalid attributes (e.g. column: nil) and expect the result to not return true; maybe you want to ensure that it IS created with valid attributes.
You can btw, use `attributes_for(:factory_name)`` since you're using FactoryGirl. And no you don't necessarily need to specify the user's id in your lesson plan factory; unless you always want it to reference user 1. You can simply reference user with no value. Check out http://everydayrails.com/2012/03/12/testing-series-intro.html and especially parts 3-5 for an introduction to testing with RSPec.. I found this a pretty easy to follow guide when I was getting started.

Rails: Avoiding duplication errors in Factory Girl...am I doing it wrong?

Suppose I have a model user, which has a uniqueness constraint on the email field
If I call Factory(:user) once all is well, but if I call it a second time it'll fail with an "entry already exists" error.
I'm currently using a simple helper to search for an existing entry in the DB before creating the factory...and calling any factory I make through that helper.
It works, but it's not entirely elegant, and considering how common I assume this problem must be, I'm guessing there's a better solution. So, is there an inbuilt way in factory girl to return_or_create a factory, instead of just charging ahead with create()? If not, how do most folk avoid duplicate entries with their factories?
Simple answer: use factory.sequence
If you have a field that needs to be unique you can add a sequence in factory_girl to ensure that it is never the same:
Factory.define :user do |user|
sequence(:email){|n| "user#{n}#factory.com" }
user.password{ "secret" }
end
This will increment n each time in order to produce a unique email address such as user52#factory.com. (See https://github.com/thoughtbot/factory_girl/wiki/Usage for more info)
However this isn't always great in Rails.env.development...
Over time I have found that this is not actually the most useful way to create unique email addresses. The reason is that while the factory is always unique for your test environment it's not always unique for your development environment and n resets itself as you start the environment up and down. In :test this isn't a problem because the database is wiped but in :development you tend to keep the same data for a while.
You then get collisions and find yourself having to manually override the email to something you know is unique which is annoying.
Often more useful: use a random number
Since I call u = Factory :user from the console on a regular basis I go instead with generating a random number. You're not guaranteed to avoid collisions but in practice it hardly ever happens:
Factory.define :user do |user|
user.email {"user_#{Random.rand(1000).to_s}#factory.com" }
user.password{ "secret" }
end
N.B. You have to use Random.rand rather than rand() because of a collision (bug?) in FactoryGirl [https://github.com/thoughtbot/factory_girl/issues/219](see here).
This frees you to create users at will from the command line regardless of whether there are already factory generated users in the database.
Optional extra for making email testing easier
When you get into email testing you often want to verify that an action by a particular user triggered an email to another user.
You log in as Robin Hood, send an email to Maid Marion and then go to your inbox to verify it. What you see in your inbox is something from user_842#factory.com. Who the hell is that?
You need to go back to your database to check whether the email was sent / received by whomever you expected it to be. Again this is a bit of a pain.
What I like to do instead is to generate the email using the name of the Factory user combined with a random number. This makes it far easier to check who things are coming from (and also makes collisions vanishingly unlikely). Using the Faker gem (http://faker.rubyforge.org/) to create the names we get:
Factory.define :user do |user|
user.first_name { Faker::Name::first_name }
user.last_name { Faker::Name::last_name }
user.email {|u| "#{u.first_name}_#{u.last_name}_#{Random.rand(1000).to_s}#factory.com" }
end
finally, since Faker sometimes generates names that aren't email-friendly (Mike O'Donnell) we need to whitelist acceptable characters: .gsub(/[^a-zA-Z1-10]/, '')
Factory.define :user do |user|
user.first_name { Faker::Name::first_name }
user.last_name { Faker::Name::last_name }
user.email {|u| "#{u.first_name.gsub(/[^a-zA-Z1-10]/, '')}_#{u.last_name.gsub(/[^a-zA-Z1-10]/, '')}_#{Random.rand(1000).to_s}#factory.com" }
end
This gives us personable but unique emails such as robin_hood_341#factory.com and maid_marion_10#factory.com
Here's what I do to force the 'n' in my factory girl sequence to be the same as that object's id, and thereby avoid collisions:
First, I define a method that finds what the next id should be in app/models/user.rb:
def self.next_id
self.last.nil? ? 1 : self.last.id + 1
end
Then I call User.next_id from spec/factories.rb to start the sequence:
factory :user do
association(:demo)
association(:location)
password "password"
sequence(:email, User.next_id) {|n| "darth_#{n}#sunni.ru" }
end
I found this a nice way to be sure the tests will always pass.
Otherwise you can not be sure the 100% of the times you will create a unique email.
FactoryGirl.define do
factory :user do
name { Faker::Company.name }
email { generate(:email) }
end
sequence(:email) do
gen = "user_#{rand(1000)}#factory.com"
while User.where(email: gen).exists?
gen = "user_#{rand(1000)}#factory.com"
end
gen
end
end
If you only need to generate a few values for attributes, you can also add a method to String, which keeps track of the prior strings used for an attribute. You could then do something like this:
factory :user do
fullname { Faker::Name.name.unique('user_fullname') }
end
I use this approach for seeding. I wanted to avoid sequence numbers, because they do not look realistic.
Here the String extension which makes this happen:
class String
# Makes sure that the current string instance is unique for the given id.
# If you call unique multiple times on equivalent strings, this method will suffix it with a upcounting number.
# Example:
# puts "abc".unique("some_attribute") #=> "abc"
# puts "abc".unique("some_attribute") #=> "abc-1"
# puts "abc".unique("some_attribute") #=> "abc-2"
# puts "abc".unique("other") #=> "abc"
#
# Internal:
# We keep a data structure of the following format:
# ##unique_values = {
# "some_for_id" => { "used_string_1" : 1, "used_string_2": 2 } # the numbers represent the counter to be used as suffix for the next item
# }
def unique(for_id)
##unique_values ||= {} # initialize structure in case this method was never called before
##unique_values[for_id] ||= {} # initialize structure in case we have not seen this id yet
counter = ##unique_values[for_id][self] || 0
result = (counter == 0) ? self : "#{self}-#{counter}"
counter += 1
##unique_values[for_id][self] = counter
return result
end
end
Caution: This should not be used for lots of attributes, since we track all prior strings (optimizations possible).

Rails 3 Custom Validator Problem

I need to apply validation to a Model so that 2 integer values in the record, minimum and maximum, form an inclusive range (ex. 2 and 3 are ok, but 4 and 1 are not). From what I understand, since I need to validate 2 values against each other in the same record, I have to use ActiveModel::Validator (and not ActiveModel::EachValidator). So, I try the following:
Model (app/models/position.rb):
class Position < ActiveRecord::Base
validates_with InclusiveRangeValidator
end
Validator (app/lib/validators/inclusive_range_validator.rb):
class InclusiveRangeValidator < ActiveModel::Validator
def validate(record)
record.errors[:base] << "#{record.minimum} and #{record.maximum} do not form a valid inclusive range." unless record.minimum < record.maximum
end
end
I've read that Rails 3.0.5 doesn't automatically load the lib directory anymore, so I added this line in config/application.rb:
config.autoload_path += %W({config.root}/lib)
And then I reset the rails server so it'll take the change to config/application.rb. I run my unit tests, which tries to exercise the model to prove this validation works. I get this error:
uninitialized constant: Position::InclusiveRangeValidator (NameError)
What I think is happening is that Rails is not recognizing/finding my custom validator class, and so it assumes InclusiveRangeValidator is a constant in the class that it's referenced in. But, I thought the change I made to config/application.rb would put my validator in the load path so that it would be available.
I've gone through the other posts on StackOverflow and didn't come up with a solution, and I've read the API docs on validators, to no avail. I've got to be doing something simple and stupid, but I can't see what the issue is. Any help?
EDIT:
After more searching, I discovered that I don't need a custom validator at all, as I can accomplish the same goal with this:
validates :minimum, :numericality => {:greater_than_or_equal_to => 0 }
validates :maximum, :numericality => {:greater_than => :minimum }
However, the question still remains as to why Rails can't locate the custom validation class.
Once, I changed the line in application.rb to:
config.autoload_paths += %W[#{config.root}/lib/validators/]
Rails was able to find the right path to load my custom validator. I made the mistake of assuming Rails would automatically recurse the directory structure, this is evidently not the case.

rspec testing association

I want to test that a staff member is associated with a company in my rspec controller tests.
I would like to end up with this in my create action of the staff controller:
staff.companies << current_company
Where current_company is collected from a session variable.
How do I write a test for this?
I've got these models
class Company < ActiveRecord::Base
has_many :employees
has_many :staff, :through => :employees
end
class Employee < ActiveRecord::Base
belongs_to :company
belongs_to :staff
end
class Staff < ActiveRecord::Base
has_many :employees
has_many :companies, :through => :employees
end
The following test is my attempt to spec the assocation and it fails when I enter in the association code:
it "should belong to the current_company" do
staff.should_receive(:companies)
post :create
end
If I enter the 'staff.companies << current_company' code in my controller I get this error when running that test:
Failure/Error: post :create
NoMethodError:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.<<
Staff controller create method:
def create
#staff = Staff.new(params[:staff])
if #staff.save
#staff.companies << current_company
redirect_to staff_index_path, :notice => "Staff created successfully!"
else
#company = #staff.firm || current_company
flash[:alert] = "Staff failed to create"
render "new"
end
end
I would use a different approach, since testing that the model should receive a certain message couples your tests too tightly to the implementation. Do you really care whether companies receives #<< or some other method?
Really, what you want to test is whether the user's company is recorded when they post to the page. It doesn't matter how it was recorded. So I'd do something like this:
it "should add the company to the user's list of companies" do
lambda do
post :create
end.should change(staff.companies, :count).from(0).to(1)
staff.companies.map(&:name).should include("Acme, Inc.")
end
This is testing behavior instead of implementation. The advantage is that your test wont fail when someone changes that << to the equivalent push. It also has the advantage of being clearer about your intention and therefore better documenting the code.
If you're in your controller spec, I would use stub_chain
staff.stub_chain(:company, :<<).once.and_return(true)
which will mock out the company call AND the << call AND expect it to be called once.
(At least, that .once should work with stub_chain...)
You can test it with :
staff.should have(1).company
Or if the staff already has other companies, get the count and test for have(count+1).companies.
The problem with the code is that once you stub out a method - it no longer exists on the model anymore.
You have stubbed out the "companies" method (when you set the expectation on it) and it now, no-longer calls the actual, real companies association on the model but the stub that you have created... which returns nil (because you didn't set a returns value on it).
Then, when you try to put a company into this new, null method using << it says it can't do that.
To get around it you can do what you did which is to set a returns value:
staff.should_receive(:companies).and_return([])
which will then make sure that:
#staff.companies << current_company
will not fail with the horrible nil error (because there's and actual, real array for the company to go into).
But really the best thing to do is as the previous people have suggested and test what you actually really need to test - which is that saving a staff with companies will cause a new company to get saved to the db.

rails 3: testing custom config variables

I want to write a few packaged plugins that I can pull into a project and configure for the specific project. For example, I may have a rolodex plugin in which i want the person's birthday to be configurable as required or not required.
in config/initializers/rolodex.rb
Rolodex::Application.config.require_birthday = true
Then in model Person.rb
class Person << ActiveRecord::Base
validates_presence_of :birthday, :if => Proc.new{ |p| Rolodex::Application.config.require_birthday}
...
a little bumpy for an approach maybe - if there's a more idiomatic way, holler.
but here's my question. i'm trying to write unit tests to verify that the config switch works. but i can't. if i write a unit test that sets ..require_birthday = false, it's too late, because the class has already been loaded and the validator defined based on the config values in the initializers. how am i supposed to test this?
it "should be strange"
Rolodex::Application.config.should_receive(:require_birthday=).with(true)
require Rails.root.join("config/initializers/rolodex")
end