Testing Internationalised Dates in Rspec - ruby-on-rails

I have some business logic in a controller that I want to test that involves setting two values to today's date and yesterday's date.
Initially I had a passing test that essentially looked like this;
controller:
def wibble
#start_time = Date.yesterday
end
test:
it 'blah blah'
get :wibble
assigns(:start_date).should eq(Date.yesterday)
end
But a new requirement has been added that means the date should be i18n'd, which means that the controller is returning back something different.
My Thoughts
I had thought about mocking the variables, because I could only care that they are set to something, but then the business logic of Today and Yesterday isn't being exercised.
I also considered forcing i18n on the test, but this seems way to brittle.
Can anyone suggest a good way to test this?

Related

Select Model based on year variable

I am building a new app in Rails for an internal project that changes slightly each year based on the requirements of our clients. Any changes between years will occur within the models (add/remove columns, formatting, reports, etc). My plan is to build it to the requirements for this year and going forward each year I will create a new model and migration (e.g. Sample2019Record, Sample2020Record) that will encapsulate the requirements for that year. The app also needs to render previous year data and all the data is scoped based on the year meaning there is no need to render or query multiple years data. I would prefer not to create a new app each year since that is more apps that need to be maintained.
So my idea is to include the year into the URL (/2018/sample/new or /sample/new?year=2018) and parse the model based on the year ("Sample#{year}Record"). Can rails handle this safely and is there a Gem that can help assist with this approach?
Here is what I came up with, thanks for the advice.
routes.rb
get '/:year/samples', to: 'samples#index', as: :samples, defaults: { year: Time.current.year }
Routes will always default to the current year
application_controller.rb
before_action :check_year
def check_year
if params.has_key?(:year)
if "Sample#{params[:year]}Record".safe_constantize.nil?
redirect_to root_path(year: Time.current.year), notice: "Invalid Year"
end
else
redirect_to root_path(year: Time.current.year), notice: "Invalid Year"
end
end
def get_sample_record(year=Time.current.year)
"Sample#{year}Record".safe_constantize
end
Added a before_action to check the year parameter and added the get_sample_record method to safely constantize the record that can be called from any controller with an optional year like so:
sample_controller.rb
sample_2018_record = get_sample_record
sample_2018_record.count
#> 304
sample_2017_record = get_sample_record 2017
sample_2017_record.count
#> 575683
The result will be nil if an invalid year is passed so I will handle the check in the controller.
As #DaveNewton said, this seems like it should work fine so long as you keep data corresponding to different years' requirements in different tables. A few other observations:
Rails has a helper method constantize for parsing a model from a string:
klass_name = "Sample#{year}Record"
record = klass_name.constantize.new()
will make the variable record an instance of your class corresponding to the year variable. You may find it helpful to use a Factory pattern to encapsulate the process.
Also. be careful how you name and organise your files. You may find this thread helpful when working with the Rails infectors for classes with numbers in their names. A big part of working with Rails is allowing its magic to work for you rather than unwittingly trying to work against it.
As a general rather than Rails-specific piece of advice, I'd also give a considerable amount of thought to how you could define a common public interface for records that will persist across years. A codebase featuring things like
if record.instance_of? Sample2018Record
record.my_2018_method
elsif record.instance_of? Sample2019Record
record.my_method_only_relevant_to_2019
...
will become very difficult to reason about, especially for developers who join after a couple of years. Ruby has extremely powerful tools to help you duck type very effectively.

How does Rspec 'let' helper work with ActiveRecord?

It said here https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/helper-methods/let-and-let what variable defined by let is changing across examples.
I've made the same simple test as in the docs but with the AR model:
RSpec.describe Contact, type: :model do
let(:contact) { FactoryGirl.create(:contact) }
it "cached in the same example" do
a = contact
b = contact
expect(a).to eq(b)
expect(Contact.count).to eq(1)
end
it "not cached across examples" do
a = contact
expect(Contact.count).to eq(2)
end
end
First example passed, but second failed (expected 2, got 1). So contacts table is empty again before second example, inspite of docs.
I was using let and was sure it have the same value in each it block, and my test prove it. So suppose I misunderstand docs. Please explain.
P.S. I use DatabaseCleaner
P.P.S I turn it off. Nothing changed.
EDIT
I turned off DatabaseCleaner and transational fixtures and test pass.
As I can understand (new to programming), let is evaluated once for each it block. If I have three examples each calling on contact variable, my test db will grow to three records at the end (I've tested and so it does).
And for right test behevior I should use DatabaseCleaner.
P.S. I use DatabaseCleaner
That's why your database is empty in the second example. Has nothing to do with let.
The behaviour you have shown is the correct behaviour. No example should be dependant on another example in setting up the correct environment! If you did rely on caching then you are just asking for trouble later down the line.
The example in that document is just trying to prove a point about caching using global variables - it's a completely different scenario to unit testing a Rails application - it is not good practice to be reliant on previous examples to having set something up.
Lets, for example, assume you then write 10 other tests that follow on from this, all of which rely on the fact that the previous examples have created objects. Then at some point in the future you delete one of those examples ... BOOM! every test after that will suddenly fail.
Each test should be able to be tested in isolation from any other test!

How to stub out Date that is implicitly referenced by years.ago in RSpec?

I know that I can stub out a method on Date like so:
allow(Date).to receive(:today).and_return Date.new(2015,11,10)
So now if within my spec the code calls Date.today I can be assured that it will return a Date object with the value of 11/10/2015.
I have a scope that utilizes years.ago.to_date. The user specifies the number of years ago for the scope: Ex: 5.years.ago, 2.years.ago.
I am trying to test this scope. In order to do so I need to control the Date that years.ago is referencing. For instance, I would always want the Date to be 1/1/2010. This way I will know that 5.years.ago will return 1/1/2005, and 2.years.ago would return 1/1/2008.
The issue is that I do not know what to stub out. I do not know how to keep the Date consistent which years.ago uses.
Hopefully this makes sense. I just need to control the Date that years.ago uses. How can I stub that out?
I looked a bit at ActiveSupport::Duration, but I'm not sure if that is the right place to look.
You should check timecop
Then in your tests, you could freeze the date to your desired value as follows:
describe "some set of tests to mock" do
before do
Timecop.freeze(2010, 1, 1)
end
after do
Timecop.return
end
it "should do blah blah blah" do
end
end
You can usually control what Ruby and Rails use for the current time by stubbing Time.now:
allow(Time).to receive(:now).and_return(Time.local 2016, 9, 6, 16, 51)
That does work for years.ago.
If your code, or the framework code you use, uses both Time.now and Date.today, however, timecop is easier.
If you use timecop, be aware that it's easy to forget to Timecop.return, which can screw up subsequent tests. Prefer timecop's safe mode.

Date Based Automated Actions in Rails4

I'm developing a Rails4 app and I have lots of date attributes in a model. For example;
first_payment_date
last_payment_date
first_application_date
last_application_date
first_result_validation_date
last_result_validation_date
etc.
I want to automate things a little bit and want my application act with these dates. For example, user's won't be able to do payment, after the last_payment_date, or they will not be able to make an application before first_application_date.
What is the best approach to plan this kind of thing? I heard about "state machines" and state_machine GEM but I'm not sure if it's the right thing for me.
Thanks.
If these dates are in the User model, you can create several helper methods inside it to return true of false based on your conditions:
def can_deliver_payment?
self.last_payment_date > Date.today
end
def can_make_application?
self.first_application_date > Date.today
end
# etc
So now when you have an instance of User, you can check these conditions on a more readable way.

what RSpec approach could I use for this requirement

Background: So I have roughly (Ruby on Rails app)
class A
def calculate_input_datetimes
# do stuff to calculate datetimes - then for each one identified
process_datetimes(my_datetime_start, my_datetime_end)
end
def process_datetimes(input_datetime_start, input_datetime_end)
# do stuff
end
end
So:
I want to test that calculate_input_datetimes algorithms are working
and calculating the correct datetimes to pass to process_datetimes
I know I can STUB out process_datetimes so that it's code won't be
involved in the test
QUESTION: How can I setup the rspec test however so I can specifically
test that the correct datestimes were attempted to be passed over to
process_datetimes, So for a given spec test that process_datetimes was
called three (3) times say with the following parameters passed:
2012-03-03T00:00:00+00:00, 2012-03-09T23:59:59+00:00
2012-03-10T00:00:00+00:00, 2012-03-16T23:59:59+00:00
2012-03-17T00:00:00+00:00, 2012-03-23T23:59:59+00:00
thanks
Sounds like you want should_receive and specifying what arguments are expected using with, for example
a.should_receive(:process_datetimes).with(date1,date2)
a.should_receive(:process_datetimes).with(date3,date4)
a.calculate_input_datetimes
There are more examples in the docs, for example you can use .ordered if the order of these calls is important

Resources