How can i compare the date with rspec? - ruby-on-rails

I am create a variable #now to keep the value lets say '2023-01-04T14:30:00-04:00' but the time is not the same, i believe that is recalculating mseconds
it 'ok' do
#now = DateTime.now
payload = {
start: #now
}
put "/api/v1/#{#id}", params: payload
expect(#element.start).to eq(#now)
i am getting this error
Diff:
## -1 +1 ##
-Wed, 04 Jan 2023 15:23:39 +0000
+Wed, 04 Jan 2023 15:23:38.324000000 UTC +00:00

whenever you test something related with Date or Time, it is a very good practice (if not the only) to freeze the time as shown in the previous answer.
It is the only way to guarantee the same timing result no matter when you run the test. Id suggest you to looking into date/time testing scenarios and how ActiveSupport::Testing::TimeHelpers is used.

You're almost certainly making the classic beginner mistake of confusing the instance variables of your test/spec with those that exist in the controller.
They belong to a completely different class and are quite often running in a completely different process. ActiveRecord models don't have a "live" database connection and are just a snapshot of the data when it was queried.
If you want to see changes performed in the controller reflected in your tests you need to refresh the attributes of the model with a database query:
it 'updates the starting time' do
payload = {
start: Time.at(0)
}
expect {
put "/api/v1/#{#id}", params: payload
#element.reload
}.to_change(#element, :start).to be_within(1.second).of(payload[:start])
end
To make these kinds of errors more obvious its often useful to use times in your tests that are way off from the current time (like the start of unix time) so that you don't end up with a false positive caused by a time thats a few miliseconds off.

Related

TimeWithZone & Time.zone.now integration test fails

In a controller method I set a user's variable activation_sent_at equal to Time.zone.now when an activation email is sent to that user. On the development server this seems to work (although time expressions in my application are 2 hours behind on the local time of my computer).
I want to include an integration test that tests whether activation_sent_at indeed gets set properly. So I included the line:
assert_equal #user.activation_sent_at, Time.zone.now
However, this produces the error:
No visible difference in the ActiveSupport::TimeWithZone#inspect output.
You should look at the implementation of #== on ActiveSupport::TimeWithZone or its members.
I think it's suggesting to use another expression for Time.zone.now in my test. I've looked at different sources, including http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html, but am not sure what to do here. Any suggestions what could be causing this error?
Additional info: Adding puts Time.zone.now and puts #stakeholder.activation_sent_at confirms the two are equal. Not sure what generates the failure/error.
The issue is that the 2 dates are very close to each other but not the same. You can use assert_in_delta
assert_in_delta #user.activation_sent_at, Time.zone.now, 1.second
For RSpec, a similar approach would be to use be_within:
expect(#user.activation_sent_at).to be_within(1.second).of Time.zone.now
The problem is that your times are very close but not quite equal. They are probably off by a few fractions of a second.
One solution to issues like this is a testing gem called timecop. It gives you the ability to mock Time.now so that it will temporarily return a specific value that you can use for comparisons.
The reason is because Time.now or Time.zone.now include milliseconds (when you do a simple put to print the time it doesn't show milliseconds). However, when you persist the timestamp in the database these milliseconds likely get lost unless the db field is configured to store milliseconds. So when you read the value from the db it will not include milliseconds, hence the times are slightly different.
One solution is to remove milliseconds from Time.now. You can do this like so Time.now.change(usec: 0). This should fix the error in the tests.

update_attributes works locally, but doesn't affect the change when deployed to Heroku

I have a simple rails webservice that exposes a JSON API that allows clients to POST a new record. When the POST is processed, it first creates the referenced record, but also updates some related records at the same time.
#foo = Foo.new(foo_params)
if #foo.save
related_list = Foo.where(:some_id => foo_params[:list])
related_list.each do |related|
# <figure out some stuff here>
related.update_attributes(stuff)
end
# debug spew
#test = Foo.find(a_related_record_id)
puts #test.as_json
format.json { render json: #foo.as_json }
end
I recognize this is a bad two-phase commit pattern, and it's abusive of the typical REST model, but I inherited this code. I'm going to fix it eventually; this is all part of the "fixing" plan in stages.
This code works perfectly on a local test box. When deployed to Heroku, I put in debug spew that shows the model apparently being updated after update_attributes is called. However, somehow, the model isn't actually updated. When I check on the "related" records afterwards using a webpage, none of them have been updated with the "stuff" changes. I'm quite baffled by this since the debug spew appears to show the record being updated. Any insights?
Update
One thing I just noticed as I went back through my debug spew is that the updated_at datetime never changes, even though the field in the record appears to have. You can see from the lines below that the call was made around 8:04:15 UTC. Also, I verified that update_attributes was returning true, so there was no error as far as I could tell.
Before update_attributes is called:
←[36m2012-09-20T08:04:15+00:00 app[web.1]:←[0m { "created_at"=>Thu, 20 Sep 2012 00:43:45 UTC +00:00, "somefield"=>"", "id"=>2, "updated_at"=>Thu, 20 Sep 2012 06:22:07 UTC +00:00}
After update_attributes is called:
←[36m2012-09-20T08:04:15+00:00 app[web.1]:←[0m { "created_at"=>Thu, 20 Sep 2012 00:43:45 UTC +00:00, "somefield"=>"10000848364", "id"=>2, "updated_at"=>Thu, 20 Sep 2012 06:22:07 UTC +00:00}
After much painstaking research, I managed to figure out the source of the issue, and the fix, but I'm not clear why this is happening. Still, it's unblocking me for now, and that suffices.
In the code above, within the
# <figure out some stuff here>
block. There was code that I abstracted out in the original question since I didn't think it was pertinent. Turns out it was. Specifically, there was a line where I was doing a string concatenation:
some_string << #foo.somestringfield
This one line, for some reason, caused
related.update_attributes(stuff)
not to save to the database. This was 100% reproducible (trust me, I tested it about 10 times to be sure). The fix was simple:
some_string = some_string + #foo.somestringfield
That, seriously, fixed the problem. I did not debug deep into the guts of the in-place concatenation within ruby to determine the root cause, but if someone else ever encounters this problem, well, now you know.
Looks like in general this behavior (update_attributes saves on local but not on Heroku) can be troubleshooted by making sure you're not executing a function inside of the call to update_attributes.
For me, calling update_attributes(:heartbeat => Time.now) worked on local and failed to save on Heroku. Using the answer above, I took a guess and resolved the issue by saving Time.now to a variable before calling update_attributes:
heartbeat = Time.now
thing.update_attributes(:heartbeat => heartbeat)
This code saved properly on Heroku.
This should be a comment, but due to reputation rules, I'm 'answering.'

Temporarily setting system time with Ruby for unit testing

I've created a custom validation on a Apt model that verifies that an appointment can't be booked for the next day if the form submission is done after 3 PM and I'm trying to determine the best way to run the unit tests to verify that my custom validation is working correctly.
Currently, my test contains an if statement to to check if the test is run before or after 3 PM and uses a different test based on the current system time. However, I'd like to be able to test both cases no matter what the system time is.
test "booking appointment for tomorrow before/after three pm" do
a = appointments(:aptone)
# Set appointment date to tomorrow
a.apt_date = DateTime.current + 1.day
if (Time.current.hour >= 12)
assert(a.invalid?, 'Appointment CANNOT be booked after 3 PM')
elsif
assert(a.valid?, 'Appointment CAN be booked before 3 PM')
end
end
I'm wondering if there is a way to temporarily set the system time while running the test so that I can test both assertions regardless of when the tests are ran.
I can strongly recommend the timecop gem which was written for exactly the requirements you mentioned. Have a look at the documentation on the github page, specifically the freeze method (or travel, but it is better to test against a frozen system time in your example).
An easier approach would be to use a mocking framework to stub the response from the DateTime.current call to return your hardcoded value.
That avoids messing with your system clock.
System Time is related to your OS and not to your application. So if you wanted to set it then you can set it using
date --set="STRING"
in Linux.

Working with and testing dates in rails

My application uses dates a lot. A lot of ajax calls and urls involve datetimes and I find the typical format '1920-10-10 18:30:00' to be unfriendly for these purposes. I work around this by creating a helper method that basically strips the unnecessary characters out of the date (192010101830) and another method for converting the string back in to a date object.
When I build a url it goes something like this:
=link_to "Send Date", thing_my_date_path(date_to_string(DateTime.now))
Then when the thing_date action receives, it converts the parameter back in to a datetime object
def my_date
#date = string_to_date(params[:mydate])
....
end
This works fine in development. However I am completely open to other suggestions.
The problem is when I go to test my application. Tests fail because the helper methods for date_to_string and string_to_date are not present. I could include them in the tests but I feel like they should be kept separate.
So I'm looking for
a) a better way to pass dates around, and more importantly
b) a method of testing an action that is dependent on helper methods.
There are built-in methods for that methinks.
> DateTime.now.to_s(:number)
=> "20110429162748"
> DateTime.parse("20110429162748")
=> Fri, 29 Apr 2011 16:27:48 +0000
Hope that helps..

How do I work with Time in Rails?

I've been pulling my hair out trying to work with Time in Rails. Basically I need to set all time output (core as well as ActiveSupport) to the server's local time -- no GMT, no UTC, etc. I've seen various posts relating to Time, but they usually involve someone's need to set it for each user. Mine isn't nearly as complex, I simply want consistency when I use any Time object. (I'd also appreciate not receiving errors every 3 seconds telling me that I can't convert a Fixnum (or some other type) to string -- it's Ruby, just do it!)
I also seem to be getting drastically different times for Time.new vs the ActiveSupport 1.second.ago. Anyway, does anyone have any quality suggestions as regards working with Time in Rails?
If you just want Time objects to be consistent, then why not stick with UTC? I just tried Time.new and 1.second.ago using script/console and I get the same output (give or take a second for typing the command). How are you doing it?
Somewhere in your initializers, define the format(s) that you want to use.
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(:default => '%m/%d/%Y %H:%M')
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(:my_special_format => '%H:%M %p')
Then when you want to print a Time object, it works like the following example. Notice that the Time object in my console is already aware of my time zone. I'm not performing any magical transformations here.
>> t = Time.now
=> Wed Jul 15 18:47:33 -0500 2009
>> t.to_s
=> "07/15/2009 18:47"
>> t.to_s(:my_special_format)
=> "18:47 PM"
Calling Time#to_s uses the :default format, or you can pass in the name of the format you'd rather use like I did with :my_special_format.
You can see the various options for formatting a Time object here.
If u don't want to store each user time setting, the only solution is to use javascript time system because it work on user client time. For example i have an application that each time user try it, the app will create some example data with each data have a initial date value "today". At first time, it confuse me a lot because my host server is in australia and lot of user is on western part, so sometime the initial date value is not "today", it said "yesterday" because of different time region.
After a couple day of headache i finally take decision to JUST use javascript time system and include it in the link, so when user click the "try now" link it will also include today date value.
<% javascript_tag do -%>
var today = new Date();
$("trynow").href = "<%= new_invitation_path %>?today=" + today.toLocaleString();
<% end -%>
Add the following to config/environment.rb to handle time correctly and consistently all the time within the context of Rails. It's important to know that it will store your times to the database in UTC -- but this is what you want -- all the conversion is done automatically.
config.time_zone = 'Pacific Time (US & Canada)'
You can run rake time:zones:local from your Rails root directory to get a list of valid time zone strings in your area.
A quick addition to the DATE_FORMAT solution posted above. Your format can be a string, in which case it works as noted above by calling strftime, but you can also define the format as a lambda:
CoreExtensions::Time::Conversions::DATE_FORMATS.merge! :my_complex_format => lambda {|time|
# your code goes here
}

Resources