FactoryGirl Rspec creating invalid instances - ruby-on-rails

So I have an application that revolves around events on a calendar. Events have a starts_at and ends_at. A new requirement has been added to restrict the creation of new events to only be in the future, more specifically >= midnight today. So naturally, I modified my validate method to check for this at instantiation. This however creates a problem when testing with RSpec using FactoryGirl.
A lot of tests require checking certain requirements with events in the past. Problem is, I can no longer create those elements in the past since it fails my validation. So I've found a few ways to get around this with
allow(instance)to receive(:starts_at).and_return(some_future_time)
or
expect_any_instance_of(Event).to receive(:check_valid_times).and_return(true)
Here's some simple code from the app
#Model
class Event < ActiveRecord::Base
validate :check_for_valid_times
private:
def check_for_valid_times
if starts_at.present? && (starts_at < Time.now.at_beginning_of_day)
errors.add(:base, I18n.t('event.start_time_before_now'))
end
unless starts_at.present? && (starts_at < ends_at)
errors.add(:base, I18n.t('event.end_time_before_start'))'
end
end
end
My factory
#factory
require 'factory_girl'
FactoryGirl.define do
factory :event do
starts_at { 2.hour.from_now.to_s }
end
end
The question: Is there a nice clean way to do this instead of having to go handle each creation of an event and dealing with them specifically in all my tests? Currently I've having to go deal with quite a few of them in varying ways.
Let me know if there are other parts of the code I should be posting up.
As always, Thanks in advance for your help!

The way I generally solve problems like this is the timecop gem.
For instance:
Timecop.freeze 1.hour.ago do
create(:event)
end
You can then place these in let blocks, or in a helper in your spec/support/events.rb to keep it DRY and maintainable.
Using timecop, you can actually simulate a test completely. So create the event correctly at the time in the past that you care about, then return time to normal and see that your tests show that the event is in the past and cant be altered, and so on. It allows complete, accurate and understandable time-sensitive tests. RSpec helpers, shared contexts and before/let blocks can handle keeping the tests clean.

Related

Testing using time helpers

I am using the standard tools included in Rails 6 for testing. It's a very simple test, but it seems the freeze_time is not working, and the error code is quite difficult to discern a cause from.
Here is the test I am executing:
Here is the error after running the test:
When you create a new Person the value for created_at should be set (assuming it has timestamps applied), but since you're getting nil instead it is almost certain your Person creation fails. Likely due to validations errors when it tries to save. You could look at the model's error entries to be sure.
To get the error to show up for viewing:
class PersonTest < ActiveSupport::TestCase
test 'created at matches current time' do
freeze_time
assert_equal(Time.current, Person.create!.created_at)
end
end
If it is a validation error, you can bypass those:
class PersonTest < ActiveSupport::TestCase
test 'created at matches current time' do
freeze_time
person = Person.new.save(validate: false)
assert_equal(Time.current, person.created_at)
end
end
There are two things wrong with this though.
You want to avoid saving to the DB if at all possible during tests to keep them performant.
You are actually testing Rails's built-in functionality here. This is a test you should not be performing. There may be a better test to check that the Rails timestamps have been applied to your Person model, but that's not one I've ever written before (and I write tests for everything). There is no way to fat-finger the timestamps away in a commit, so testing for their existence feels way overkill.

Observing conditions across multiple models

I'm building a flow whereby a user can administer an event, specifically doing the following:
Register attendees
Attach photos
Attach fitness information
Each of these currently happens in a seperate controller, and can happen in any order.
Having completed all three, I'd then like to generate an email out to all attendees with links ot the photos, etc.
I'm having trouble finding the best approach to check against the three conditions listed above. Currently, I'm approaching it by creating a service called GenerateEmailsToAttendees with a method .try. This checks against the conditions, and if all are met, generates the emails: e.g:
class GenerateEmailsToAttendees
def try(event)
if event.has_some_fitness_activities? and event.has_some_attendees? and event.has_some_photos?
event.attendances.each do |attendance|
attendance.notify_user_about_write_up
end
end
end
end
The problem now is that I have this GenerateEmailsToAttendees scattered across three controllers (AttendeesController#register, PhotosController#attach and FitnessInfoController#attach). I also run the risk of duplicating the notifications to the users.
Is there a better way? Could I use an observer to watch for the three conditions being met?
I can provide more information on the model structure if it's useful.
Thanks!
How about moving your observer to a cron job? i.e: remove it from all three controllers, and just put it in a rake task and schedule it to run every week/day/hour etc on all events that have met the conditions. You should probably set a flag on the event if the email has been generated so you don't spam the same user twice. I understand that this might not be realtime but it'll definitely solve your problem. I would recommend using https://github.com/javan/whenever for managing your cronjobs.
I would put this into an after_save callback: then Rails will just take care of it automatically. You will probably need some system to ensure that this only happens once. I would do something like this:
add a new boolean field to track whether the event has all of the required "stuff" done in order to send out the email, eg "published"
when the various things that can make an event "published" happen, call a method in the Event model which tests if the event is ready to be published and currently NOT published: if it is, then update the model to be published and send the email.
eg - (i'm guessing at your join table names here)
#app/models/event_attendance.rb
after_create :is_event_publishable?
def is_event_publishable?
self.event.publishable?
end
#app/models/event_fitness_activity.rb
after_create :is_event_publishable?
def is_event_publishable?
self.event.publishable?
end
#app/models/event_photo.rb
after_create :is_event_publishable?
def is_event_publishable?
self.event.publishable?
end
#app/models/event.rb
def publishable?
if !self.published && self.fitness_activities.size > 0 and self.attendences.size > 0 and self.photos.size > 0
self.attendances.each do |attendance|
attendance.notify_user_about_write_up
end
end
end
Now you don't need anything to do with this at all in your controllers. Generally i'm in favour of keeping controllers as absolutely standard as possible.
Yes, you can create an observer that watches multiple models with a single 'after_save' callback using something like
observe :account, :balance
def after_save(record)
make your checks here
end

Is it a bad idea to write invalid FactoryGirl factories/traits?

Listening to Giant Robots Smashing Into Other Giant Robots podcast, I heard that you want your FactoryGirl factories to be minimal, only providing those attributes that make the object valid in the database. That being said, the talk also went on to say that traits are a really good way to define specific behavior based on an attribute that may change in the future.
I'm wondering if it's also a good idea to have traits defined that purposefully fail validations to clean up the spec code. Here's an example:
factory :winner do
user_extension "7036"
contest_rank 1
contest
trait :paid do
paid true
end
trait :unpaid do
paid false
end
trait :missing_user_extension do
user_extension nil
end
trait :empty_user_extension do
user_extension ""
end
end
will allow me to call build_stubbed(:winner, :missing_user_extension) in my specs in tests I intend to fail validations. I suppose I could further this explicit fail by nesting these bad factories under another factory called :invalid_winner, but I'm not too sure if that's necessary. I'm mostly interested in hearing others' opinions on this concept.
No it's not a good idea, it wont make your specs clear to understand after a while, and later when your code evolve those factory that fail today may not fail anymore, and you would have hard time to review all your specs.
It is way better to write your test for one clearly identified thing. If you want to check that saving fails with a mandatory parameter missing, just write it with your regular factory and add parameters to overwrite the values from the factory:
it 'should fail' do
create :winner, user_extension: nil
...
end

Alternatives to factory_girl

In my opinion and for my purposes, factory_girl completely sucks. Some limitations include:
No debugging support
If I include debugger statements, they are treated as model attributes. Instead of invoking the debugger, I just get strange errors.
FactoryGirl.define do
factory :admin do
name do
debugger # <-- Does not work.
Forgery(:name).full_name
end
email { Forgery(:email).address }
debugger # <-- Does not work either.
password "secret"
end
end
Limitations to associations
Am I too stupid or is there no elegant way to add two posts to a user?
FactoryGirl.define do
factory :post do
title "Foobar"
content "Some content"
end
factory :user do
name { Forgery(:name).full_name }
email { Forgery(:email).address }
# This does not work, if the Post model requires Post#user to be set.
posts [FactoryGirl.create(:post), FactoryGirl.create(:post)]
end
end
See also Factory Girl - Why are Records being continually created?
Tends to trigger strange bugs in rails
I can't remeber what happend, but often strange problems arise with factory_girl.
So given these examples. Are there any alternatives to factory_girl that do not have these issues?
I agree and found Factory Girl overly complicated for what it does.
I wrote a simpler gem a while ago which (at the time at least) was a drop in replacement for Factory Girl-based tests.
The factory definitions use much simpler Ruby and therefore behave as you would expect them to.
Check it out:
https://github.com/ginty/cranky
Why are you debugging inside the factory definition instead of in your code on the resulting objects?
And what's wrong with
user = FactoryGirl.create(:user)
2.times do
FactoryGirl.create(:post, user: user)
end
A debugger statement in a DSL can be problematic. You don't know when it will run.
FactoryGirl could run the DSL, save a representation of the factory, and use the internal representation in memory when the factory is used.
Also, there is no variable to inspect except self. The self is going to be a germ object to build the definition.
At the risk of answers all being defense of FactoryGirl, if your alternatives to FactoryGirl are DSLs to populate data, you are still going to have the problem of debugging support.
Alternatives include fixtures and just calling ActiveRecord to populate test data. Really FactoryGirl doesn't have much over ActiveRecord, it's just more symbol oriented calls, so people get to make meaningful symbol names which is all FactoryGirl was supposed to do.
You cannot put a debugger statement in the middle of a fixture, or in the middle of a hash that you are sending to a create method, but at least you won't be tempted to.
(The See also Factory Girl - Why are Records being continually created?, was an example where FactoryGirl was working perfectly, but the user told it to create four records, and then was surprised when it created four records.)
So maybe if you stick with fixtures and ActiveRecord calls things will be dumbed down enough that you won't get confused.

ActiveRecord: make all text fields have strip called on them before saving, unless specified otherwise

I've ran into various problems with various sites over the years with users putting spaces at the start/end of string and text fields. Sometimes these cause formatting/layout problems, sometimes they cause searching problems (ie search order looking wrong even though it isn't really), sometimes they actually crash the app.
I thought it would be useful, rather than putting in a bunch of before_save callbacks as i have done in the past, to add some functionality to ActiveRecord to automatically call .strip on any string/text fields before saving, unless i tell it not to, eg with do_not_strip :field_x, :field_y or something similar at the top of the class definition.
Before i go and figure out how to do this, has anyone seen a nicer solution? Just to be clear, i already know that i can do this:
before_save :strip_text_fields
def strip_text_fields
self.field_x.strip!
self.field_y.strip!
end
but i'm looking for a nicer way.
cheers, max
Here's a handy module that you could drop into lib and include in your models. It doesn't have the exceptions that you mentioned, but it looks for a strip! method which might be good enough. You could add the exceptions feature fairly easily, if needed.
# lib/attribute_stripping.rb
module AttributeStripping
def self.included(context)
context.send :before_validation, :strip_whitespace_from_attributes
end
def strip_whitespace_from_attributes
attributes.each_value { |v| v.strip! if v.respond_to? :strip! }
end
end
Use like this:
class MyModel < ActiveRecord::Base
include AttributeStripping
# ...
end
UPDATE (9/10/2013):
Revisiting this answer a couple of years later, I see how the winds have changed. There's a cleaner way to do it now. Create a module like this:
module AttributeStripper
def self.before_validation(model)
model.attributes.each_value { |v| v.strip! if v.respond_to? :strip! }
true
end
end
and set its method to be invoked at the right time in your model:
class MyModel < ActiveRecord::Base
before_validation AttributeStripper
# ...
end
This module is easier to test since it's not a mixin.
I have dealt with these sort of data integrity issues in various applications.
I used to manipulate the input like that.
But now, the best advice I have actually seen and followed is to store whatever the user types.
Then do post-processing on the backend to do the strip.
Create additional database fields (destripped) if you really want it in the database model table.
The main reason for this is one (primary) thing - when users want to revisit their data, i.e. edit, they're usually gonna expect to see what they typed in. A secondary reason is that you will avoid the possibility that your strip doesn't work right and either mangles the data or actually throw an error.
I've written a plugin for this purpose some time ago. I haven't tried it in a while and it doesn't have tests - so no guaranties that it still works. The upside would be a clean model:
class Story < ActiveRecord::Base
strip_strings :title, :abstract, :text
end

Resources