RSpec - Date should be between two dates - ruby-on-rails

How can I test a date to see if it's between two dates? I know I can do two greater-than and less-than comparisons but I want an RSpec method to check the "betweeness" of the date.
For example:
it "is between the time range" do
expect(Date.now).to be_between(Date.yesterday, Date.tomorrow)
end
I tried expect(range).to cover(subject) but no luck.

Date.today.should be_between(Date.today - 1.day, Date.today + 1.day)

Both of the syntaxes you wrote are correct RSpec:
it 'is between the time range' do
expect(Date.today).to be_between(Date.yesterday, Date.tomorrow)
end
it 'is between the time range' do
expect(Date.yesterday..Date.tomorrow).to cover Date.today
end
If you are not using Rails you won't have Date::yesterday or Date::tomorrow defined. You'll need to manually adjust it:
it 'is between the time range' do
expect(Date.today).to be_between(Date.today - 1, Date.today + 1)
end
The first version works due to RSpec's built in predicate matcher. This matcher understand methods being defined on objects, and delegates to them as well as a possible ? version. For Date, the predicate Date#between? comes from including Comparable (see link).
The second version works because RSpec defines the cover matcher.

I didn't try it myself, but according to this you should use it a bit differently:
it "is between the time range" do
(Date.yesterday..Date.tomorrow).should cover(Date.now)
end

You have to define a matcher, check https://github.com/dchelimsky/rspec/wiki/Custom-Matchers
It could be
RSpec::Matchers.define :be_between do |expected|
match do |actual|
actual[:bottom] <= expected && actual[:top] >= expected
end
end
It allows you
it "is between the time range" do
expect(Date.now).to be_between(:bottom => Date.yesterday, :top => Date.tomorrow)
end

Related

How do I pass a method with two arguments to a Minitest Spec assertion?

With Minitest Spec in Rails I'm trying to check if an ActiveSupport::TimeWithZone is in a certain range. I thought to use the between? method that takes the min and max of the range. Here's how I'm expressing that in Minitest Spec:
_(language_edit.curation_date).must_be :between?, 10.seconds.ago, Time.zone.now
but it gives me this error:
Minitest::UnexpectedError: ArgumentError: wrong number of arguments (1
for 2)
What am I doing wrong?
Looks like must_be is implemented as infect_an_assertion :assert_operator, :must_be
assert_operator
# File lib/minitest/unit.rb, line 299
def assert_operator o1, op, o2, msg = nil
msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op} #{mu_pp(o2)}" }
assert o1.__send__(op, o2), msg
end
What if you use assert directly?
Example:
class DateTest < ActiveSupport::TestCase
test "using assert with between? should work" do
a = 5.seconds.ago
assert a.between?(10.seconds.ago, Time.zone.now)
end
end
Thanks to radubogdan for showing me some of the code behind the must_be method. It looks like it's designed to be used with operators like this:
_(language_edit.curation_date).must_be :>, 10.seconds.ago
and a side-effect of this is it works with boolean methods that take one or no arguments, but not with methods that take more than one argument. I think I'm supposed to do this:
_(language_edit.curation_date.between?(10.seconds.ago, Time.zone.now)).must_equal true

Ruby - What is the difference between range's include and cover methods wrt date ranges? [duplicate]

Edit Fixed following toro2k's comment.
Range#include? and Range#cover? seem to be different as seen in the source code 1, 2, and they are different in efficiency.
t = Time.now
500000.times do
("a".."z").include?("g")
end
puts Time.now - t # => 0.504382493
t = Time.now
500000.times do
("a".."z").cover?("g")
end
puts Time.now - t # => 0.454867868
Looking at the source code, Range#include? seems to be more complex than Range#cover?. Why can't Range#include? be simply an alias of Range#cover? What is their difference?
The two methods are designed to do two slightly different things on purpose. Internally they are implemented very differently too. You can take a look at the sources in the documentation and see that .include? is doing a lot more than .cover?
The .cover? method is related to the Comparable module, and checks whether an item would fit between the end points in a sorted list. It will return true even if the item is not in the set implied by the Range.
The .include? method is related to the Enumerable module, and checks whether an item is actually in the complete set implied by the Range. There is some finessing with numerics - Integer ranges are counted as including all the implied Float values (I'm not sure why).
These examples might help:
('a'..'z').cover?('yellow')
# => true
('a'..'z').include?('yellow')
# => false
('yellaa'..'yellzz').include?('yellow')
=> true
Additionally, if you try
('aaaaaa'..'zzzzzz').include?('yellow')
you should notice it takes a much longer time than
('aaaaaa'..'zzzzzz').cover?('yellow')
The main difference is that include is checking whether object is one of range element, and cover is returning whether object is between edge elements. You can see that:
('a'..'z').include?('cc') #=> false
('a'..'z').cover?('cc') #=> true
date_range = {:start_date => (DateTime.now + 1.days).to_date, :end_date => (DateTime.now + 10.days).to_date}
date_range_to_check_for_coverage = {:start_date => (DateTime.now + 5.days).to_date, :end_date => (DateTime.now + 7.days).to_date}
(date_range[:start_date]..date_range[:end_date]).include?((DateTime.now + 5.days).to_date)
#true
(date_range[:start_date]..date_range[:end_date]).cover?((DateTime.now + 5.days).to_date)
#true
(date_range[:start_date]..date_range[:end_date]).include?(date_range_to_check_for_coverage[:start_date]..date_range_to_check_for_coverage[:end_date])
#true
(date_range[:start_date]..date_range[:end_date]).cover?(date_range_to_check_for_coverage[:start_date]..date_range_to_check_for_coverage[:end_date])
#false
Shouldn't the last line return true ?
The reason I am asking is rubocop flags a conflict when I use include? in place of cover?. And clearly, my logic (to check if the range is included in another range) does not work with cover?.
There's a huge performance difference between cover? and include?: special care when using Date ranges
For the reasons already explained: cover? just checks if your argument is between the begin and the end of the range; in include?, you are checking if your argument is actually inside the range, which involves checking every single element of the range, and not just the begin/end.
Let's run a simple benchmark.
date_range = Date.parse("1990-01-01")..Date.parse("2023-01-01");
target_date = Date.parse("2023-01-01");
iterations = 1000;
Benchmark.bmbm do |bm|
bm.report("using include") { iterations.times { date_range.include?(target_date) } }
bm.report("using cover") { iterations.times { date_range.cover?(target_date) } }
end
Results:
Rehearsal -------------------------------------------------
using include 5.466448 0.071381 5.537829 ( 5.578123)
using cover 0.000272 0.000003 0.000275 ( 0.000279)
---------------------------------------- total: 5.538104sec
user system total real
using include 5.498635 0.046663 5.545298 ( 5.557880)
using cover 0.000284 0.000000 0.000284 ( 0.000280)
As you can see, using #cover? is instantenous; you get your results in 0.000ms.
However, using #include? takes almost 5.5 seconds for the same results.
Choose carefully.

ruby rspec conversion from "should" to "expect" with block

I'm handling a set of rspec programs and pc seems to be forcing me to convert "should" questions to "expect".
Have been able to handle most, but having problems with the following rspec setup.
Most of the other 'should' formatting involves an answer should == something and is easily converted to expect(passed_in_value).to eql(returned_value).
In this case though, I believe it is passing in a block to add to a given number, however, i and unable to just convert it to
expect(end).to eql(6) or whatever the returned value should be.
Take a look and if you have any thoughts, please pass them on
it "adds one to the value returned by the default block" do
adder do
5
end.should == 6
end
it "adds 3 to the value returned by the default block" do
adder(3) do
5
end.should == 8
end
There're several methods to do that.
result = adder(3) do
5
end
expect(result).to eq(8)
expect do
adder(3) do
5
end
end.to eq(8)
block = -> do
5
end
expect(adder 3, &block).to eq(8)
Example from comments with respond_to:
it "has a #sum method" do
[].should respond_to(:sum) #old syntax
expect([]).to respond_to(:sum) #new syntax
end

Trouble comparing time with RSpec

I am using Ruby on Rails 4 and the rspec-rails gem 2.14. For a my object I would like to compare the current time with the updated_at object attribute after a controller action run, but I am in trouble since the spec does not pass. That is, given the following is the spec code:
it "updates updated_at attribute" do
Timecop.freeze
patch :update
#article.reload
expect(#article.updated_at).to eq(Time.now)
end
When I run the above spec I get the following error:
Failure/Error: expect(#article.updated_at).to eq(Time.now)
expected: 2013-12-05 14:42:20 UTC
got: Thu, 05 Dec 2013 08:42:20 CST -06:00
(compared using ==)
How can I make the spec to pass?
Note: I tried also the following (note the utc addition):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
#article.reload
expect(#article.updated_at.utc).to eq(Time.now)
end
but the spec still does not pass (note the "got" value difference):
Failure/Error: expect(#article.updated_at.utc).to eq(Time.now)
expected: 2013-12-05 14:42:20 UTC
got: 2013-12-05 14:42:20 UTC
(compared using ==)
I find using the be_within default rspec matcher more elegant:
expect(#article.updated_at.utc).to be_within(1.second).of Time.now
Ruby Time object maintains greater precision than the database does. When the value is read back from the database, it’s only preserved to microsecond precision, while the in-memory representation is precise to nanoseconds.
If you don't care about millisecond difference, you could do a to_s/to_i on both sides of your expectation
expect(#article.updated_at.utc.to_s).to eq(Time.now.to_s)
or
expect(#article.updated_at.utc.to_i).to eq(Time.now.to_i)
Refer to this for more information about why the times are different
Old post, but I hope it helps anyone who enters here for a solution. I think it's easier and more reliable to just create the date manually:
it "updates updated_at attribute" do
freezed_time = Time.utc(2015, 1, 1, 12, 0, 0) #Put here any time you want
Timecop.freeze(freezed_time) do
patch :update
#article.reload
expect(#article.updated_at).to eq(freezed_time)
end
end
This ensures the stored date is the right one, without doing to_x or worrying about decimals.
yep as Oin is suggesting be_within matcher is the best practice
...and it has some more uscases -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher
But one more way how to deal with this is to use Rails built in midday and middnight attributes.
it do
# ...
stubtime = Time.now.midday
expect(Time).to receive(:now).and_return(stubtime)
patch :update
expect(#article.reload.updated_at).to eq(stubtime)
# ...
end
Now this is just for demonstration !
I wouldn't use this in a controller as you are stubbing all Time.new calls => all time attributes will have same time => may not prove concept you are trying to achive. I usually use it in composed Ruby Objects similar to this:
class MyService
attr_reader :time_evaluator, resource
def initialize(resource:, time_evaluator: ->{Time.now})
#time_evaluator = time_evaluator
#resource = resource
end
def call
# do some complex logic
resource.published_at = time_evaluator.call
end
end
require 'rspec'
require 'active_support/time'
require 'ostruct'
RSpec.describe MyService do
let(:service) { described_class.new(resource: resource, time_evaluator: -> { Time.now.midday } ) }
let(:resource) { OpenStruct.new }
it do
service.call
expect(resource.published_at).to eq(Time.now.midday)
end
end
But honestly I recommend to stick with be_within matcher even when comparing Time.now.midday !
So yes pls stick with be_within matcher ;)
update 2017-02
Question in comment:
what if the times are in a Hash? any way to make expect(hash_1).to eq(hash_2) work when some hash_1 values are pre-db-times and the corresponding values in hash_2 are post-db-times? –
expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `
you can pass any RSpec matcher to the match matcher
(so e.g. you can even do API testing with pure RSpec)
As for "post-db-times" I guess you mean string that is generated after saving to DB. I would suggest decouple this case to 2 expectations (one ensuring hash structure, second checking the time) So you can do something like:
hash = {mytime: Time.now.to_s(:db)}
expect(hash).to match({mytime: be_kind_of(String))
expect(Time.parse(hash.fetch(:mytime))).to be_within(3.seconds).of(Time.now)
But if this case is too often in your test suite I would suggest writing your own RSpec matcher (e.g. be_near_time_now_db_string) converting db string time to Time object and then use this as a part of the match(hash) :
expect(hash).to match({mytime: be_near_time_now_db_string}) # you need to write your own matcher for this to work.
The easiest way I found around this problem is to create a current_time test helper method like so:
module SpecHelpers
# Database time rounds to the nearest millisecond, so for comparison its
# easiest to use this method instead
def current_time
Time.zone.now.change(usec: 0)
end
end
RSpec.configure do |config|
config.include SpecHelpers
end
Now the time is always rounded to the nearest millisecond to comparisons are straightforward:
it "updates updated_at attribute" do
Timecop.freeze(current_time)
patch :update
#article.reload
expect(#article.updated_at).to eq(current_time)
end
You can convert the date/datetime/time object to a string as it's stored in the database with to_s(:db).
expect(#article.updated_at.to_s(:db)).to eq '2015-01-01 00:00:00'
expect(#article.updated_at.to_s(:db)).to eq Time.current.to_s(:db)
Because I was comparing hashes, most of these solutions did not work for me so I found the easiest solution was to simply grab the data from the hash I was comparing. Since the updated_at times are not actually useful for me to test this works fine.
data = { updated_at: Date.new(2019, 1, 1,), some_other_keys: ...}
expect(data).to eq(
{updated_at: data[:updated_at], some_other_keys: ...}
)
In Rails 4.1+ you can use Time Helpers:
include ActiveSupport::Testing::TimeHelpers
describe "some test" do
around { |example| freeze_time { example.run } }
it "updates updated_at attribute" do
expect { patch :update }.to change { #article.reload.updated_at }.to(Time.current)
end
end

In RSpec - how can I test if one attribute is less (or more) than another

In my app I want to have a Car model.
It will have two fields among others: date_from and date_till (to specify a period of time someone was using it).
And I want the model to validate that date_from should be less or equal than date_till.
My model_spec.rb draft looks like this:
require 'spec_helper'
describe Car do
it {should validate_presence_of(:model)}
it {should validate_presence_of(:made)}
it "should have date_till only if it has date_from"
its "date_till should be >= date_from"
end
Obviously, I can just write a "long" test where I will try to set date_till to be greater than date_from - and the model just should be invalid. But maybe there are some elegant ways to do it?
So, how can I (using RSpec matchers) validate that one field is not greater than another?
upd:
I looked at #itsnikolay's answer and coded it like that:
it "should not allow date_till less than date_from" do
subject.date_from = Date.today
subject.date_till = Date.today - 1.day
subject.valid?.should be_false
end
Had to do it without matchers. Well, not a tragedy :)
It is generally recommended to use expect, not should.
For instance:
expect(#car.date_from).to be <= #car.date_till
Resources:
- BetterSpecs examples
- Rspec Docs
Just use <=
date_from.should be <= date_till
Or in new syntax
expect(date_from).to be <= date_till
#car = Car.create \
name: 'Buggy',
date_from: Date.today + 1.day,
date_till: Date.today + 2.day
expect(#car.date_from).to be <= #car.date_till
More details: RSpec Comparison matchers

Resources