Trouble comparing time with RSpec
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
Comparing dates with Rspec
ok, do this
expect(FactoryGirl.create(:category_page_with_parsed_results).last_parsed_result.to_date).to eql(Date.parse('2012-01-01'))
RSpec: time comparison doesn't pass
Use 'timecop' gem. This gem handles that for you.
You have a railscast dedicated to this gem: http://railscasts.com/episodes/276-testing-time-web-requests
You could do something like that:
describe 'API Key' do
it 'does expire in 30 days on api scope' do
Timecop.freeze
api_key = FactoryGirl.build(:api_key_session)
expect(api_key.expired_at).to eq(30.days.from_now)
end
end
Adjust it to your needs.
RSpec failing - date comparison
When testing validations don't use expect(object).not_to be_valid
or expect(object).to be_valid
. This is just a recipe for both false positivies and negatives as you're not actually testing a single behavior - rather you're testing every single validation at once along with your test setup.
RSpec.describe ChainPlan, type: :model do
it 'does not allow an end_time which is after the start_time' do
cp = described_class.new(
start_time: Time.current,
end_time: Time.current - 1.minute
)
cp.valid?
expect(cp.errors.messages_for(:end_time)).to include 'must be after the start date'
end
it "allows a valid start_time/end_time combo" do
cp = described_class.new(
start_time: Time.current,
end_time: Time.current.advance(10.minutes)
)
cp.valid?
expect(cp.errors).to_not have_key :end_date
end
end
Instead setup the object and then call valid?
on it to trigger the validations. Write expectations on the errors object to test the actual behavior instead of carpet bombing. For example here you would have completely missed that you where adding the errors to the key :end_date
instead of :end_time
.
The validation itself also can be improved:
class ChainPlan < ApplicationRecord
belongs_to :faction
has_many :chain_plan_slots
validates :start_time, presence: { on: :create, message: "can't be blank" }
validates :end_time, presence: { on: :create, message: "can't be blank" }
validates :faction, presence: { on: :create, message: "can't be blank" }
validate :end_date_after_start_date?, if: ->{ end_time.present? && start_time.present? }
private
def end_date_after_start_date?
errors.add(:end_time, 'must be after the start date') if end_time < start_time
end
end
Trouble with Rspec for timestamp in hashes
Since you haven't shown how response_body_json
nor Presenter#page
are defined, I cannot really answer why your current attempt doesn't work.
However, I can instead say that I would use a different approach.
There are two standard ways to write tests like this:
- Freeze time.
Assuming you're using a relatively up-to-date rails version, you can use use ActiveSupport::Testing::TimeHelpers#freeze_time
somewhere in the test, e.g. something like:
around do |example|
freeze_time { example.run }
end
it 'should return the movie_1 in page format' do
expect(response_body_json).to eql(Presenter.new(ActiveRecordObject).page)
end
If you're on an older rails version, you may need to use travel_to(Time.zone.now)
instead.
And if you're on a very old rails version (or a non-rails project!), which doesn't have this helper library, you can use timecop
instead.
- Use a fuzzy matcher for timestamps (e.g.
be_within
). Something along the lines of:
.
it 'should return the movie_1 in page format' do
expected_json = Presenter.new(ActiveRecordObject).page
expect(response_body_json).to match(
expected_json.merge(updated_at: be_within(3.seconds).of(Time.zone.now))
)
end
Rails Time inconsistencies with rspec
You should mock the now method of Time to make sure it always match the date in the spec. You never know when a delay will make the spec fail because of some milliseconds. This approach will also make sure that the time on the real code and on the spec are the same.
If you're using the default rspec mock lib, try to do something like:
t = Time.parse("01/01/2010 10:00")
Time.should_receive(:now).and_return(t)
RSpec testing Time objects
You can use RSpec's built-in be_within
matcher, which can be also be used for floating point numbers and other data types prone to slight discrepancies:
expect(@model.end_date).to be_within(1.second).of(end_date)
Related Topics
How to Execute Custom Actions After Successful Sign in with Devise
Rspec Failing Error: Expected False to Respond to 'False'
Generating an Instagram- or Youtube-Like Unguessable String Id in Ruby/Activerecord
How to Format This International Phone Number in Rails
How to Access Attributes Using Nokogiri
How to Customize Rails Activerecord Validation Error Message to Show Attribute Value
How to Use Gems Not in a Gemfile When Working with Bundler
Why Doesn't "Rails S" Work from the App Directory
Rails Carrierwave Base64 Image Upload
Rails - Local Variables Versus Instance Variables
How to Generate Links with Trailing Slash in Rails 3
Complicated Graphviz Tree Structure
What Exactly Is Arel in Rails 3.0
How to Create a Hash in Ruby That Compares Strings, Ignoring Case
Extract All Email Addresses from Some .Txt Documents Using Ruby