I'm new at coding and I'm trying to implement RSpec testing to a command I made for my slack bot using Ruby.
The command retrieves the data from the database using .where(Time.zone.now.begining_of_day..Time.zone.now.end_of_day) => showed in the code down bellow.
map through each of them and format it for the user output
Code:
Class StartCommand
def call
Answer.where(created_at: Time.zone.now.beginning_of_day..Time.zone.now.end_of_day)
.map { |answer| "#{answer.slack_identifier}:\n#{answer.body}" }
.join("\n\n")
end
end
RSpec testing:
RSpec.describe RecordCommand do
describe '#call' do
it 'retrieves data and format it' do
StartCommand.new.call
expect(Answer.where).to eq()
end
end
As you can see I'm kinda lost how to implement the code in the RSpec testing, any ideas, hints or solutions would be awesome! Help!
Im using Ruby and Ruby on Rails 4
You can use the database-cleaner gem to wipe the DB between test runs. It lets you can create records in your test case as needed. It's also a good idea to use the timecop whenever you are testing anything time-related, since it freezes your test case at any time and removes the unpredictability of Time.now. Assuming you have installed those -
it 'retrieves data and format it' do
# Freeze time at a point
base_time = Time.zone.now.beginning_of_day + 5.minutes
Timecop.freeze(base_time) do
# setup, create db records
# 2 in the current day, 1 in the previous day
Answer.create(slack_identifier: "s1", body: "b1", created_at: base_time)
Answer.create(slack_identifier: "s2", body: "b2", created_at: base_time + 1.minute)
Answer.create(slack_identifier: "s3", body: "b3", created_at: base_time - 1.day)
# call the method and verify the results
expect(StartCommand.new.call).to eq("s1:\nb1\n\ns2:\nb2")
end
end
You may also want to add an order to your where query. I don't think you can guarantee that ActiveRecord returns records in the same order each time unless you specify it (see Rails 3: What is the default order of "MyModel.all"?), and specifying it would make the test more dependable
Related
I'm writing unit test using fixtures in yaml for a ruby on rails project.
We have many tests for users being < or > to n year old.
ex:
young_kid:
id: 2
firstname: Nano
lastname: Shinonome
email: imarobot#nichijou.jp
birthdate: 2000-05-11
Is there a way to specify a relative date like 2.days.from_now So that our tests are not bound to fail once the threshold is reached ?
We're using vanilla RoR unit tests.
For dynamic fixture data, you are best off using FactoryGirl.
However, for your tests in question, you could use the the Timecop gem to fix the time during your test. Here's how your unit test might look with Timecop:
Timecop.freeze(Date.new(2018, 5, 11, 0, 0, 0)) do
assert young_kid.birthdate < 18.years.ago
end
Yes, there is a way to specify relative dates in your fixtures yaml file. Do this...
young_kid:
...
birthdate: <%= 2.days.from_now.to_date.to_s(:db) %>
A side note that fixtures are loaded really early in the test lifecycle so freezing time (using something like the Timecop gem) in your test setup block occurs after the relative dates in the fixtures get loaded, meaning that relative dates in your fixtures will always be relative to today.
This has the effect of severely limiting the usefulness of freezing time in your test.
I'm trying to mock Date.today.wday in a rake task in rspec.
Gem versions: RSpec 2.14.8 --- Rails 4.1.1 --- ruby 2.0.0
Here is a simplified fake version of my test to illustrate essentially what I'm trying to do:
describe "scheduler" do
describe ":thursday_invitations" do
let(:run_issue_invites) do
Rake::Task[:thursday_invitations].reenable
Rake.application.invoke_task :thursday_invitations
end
before do
Rake.application.rake_require 'tasks/scheduler'
Rake::Task.define_task(:environment)
Date.today.should_receive(:wday).and_return(4) ###MY NEMESIS CODE LINE
end
context "on thursday" do
it "issues invitations" do
expect(Date.today.wday).to eq(4) ###THE VERIFICATION TEST THAT KEEPS FAILING
run_issue_invites
expect(<other_stuff_to_test>).to <return_properly>
end
end
end
end
So, the real key of this is mocking out the Date.today.wday. Because I want to be able to run my spec on any day of the week, I need to mock/stub out this method to always return "4" (the day-number for Thursday in Rails). So, I initially setup my test to first verify that it is receiving a "4" (the assertion in the test). If today is, say, Friday (which it is) and I run the test, it fails saying that it expected "4" but got "5". That is, it is not returning the value that I want it to when I receive the method. I have tried stubbing with similar ineffective results. Normally, mocking is a breeze, but what seems to be the hangup is .wday which operates on Date.today.
Because this is a rake task (which I'm not as familiar with mocking), I may have to specify something further, but I haven't been able to get to the bottom of it...
Let me know if you need any other clarifying information.
I believe the reason you're not seeing the behavior you expect is that the object you are mocking is the not the same object under test.
In a Rails 4+ environment, this is what I see on the rails console:
[1]> Date.today.object_id
70104549170200
[2]> Date.today.object_id
70104552970360
The fact that the object_id is different in subsequent calls to Date.today means that each call returns a new object. So Date.today.should_receive(:wday).and_return(4) is setting an expectation on an object that will never be used again.
You'll need to rewrite your spec to ensure the same object is returned by Date.today each time. Here's one solution, omitting other parts of your example for clarity:
let!(:today) { Date.today }
before do
Date.stub(:today).and_return(today)
today.should_receive(:wday).and_return(4)
end
it "issues invitations" do
expect(Date.today.wday).to eq(4)
end
I have been struggling with a very weird Capybara/Rspec behavior that has something to do with Time.now mocking.
First, I tried to define a set of specs with some models having three date fields set to the value on DateTime.stub(:now) { DateTime.now.utc }. When executed the whole test suite by doing rspec spec the test always failed, however, when doing rspec spec/features/feature all defined specs succedded.
While researching, some people suggested to use Timecop or Delorean gems to fake the current time to avoid having problems with Capybara. I tried both with no luck. I added to the before/after each blocks something like Timecop.travel(time); all suite's specs succeeded this time, but executing the single spec file did yield failing specs .
What would be the best way to garantee deterministic outcomes from specs that deal with time as in Compute my bicycle average speed during the last month/week/day ?
I want to ALWAYS execute all such specs (defined in rspec spec/features/feature) within a given fixed date, let's say June 13, 2010 15:70:00 so that I can do, DateTime.now-1.year and I get June 13, 2009 15:70:00, and the result is always consistent regardless if I am executing it as a separate spec file or as a suite.
Example of failing test:
describe "Stats Spec" do
include IntegrationHelper
before :each do
I18n.locale = :en
DateTime.stub(:now) { DateTime.new(2012,10,11,14,50).utc }
end
context "Given Loui has been cycling during the last year" do
before(:each) do
#loui = Fabricate(:standard_user, username: 'Loui')
stub_track(:today, #loui.id, 3000)
stub_track(:this_week, #loui.id, 5000)
stub_track(:this_month, #loui.id, 8000)
stub_track(:six_months_ago, #loui.id, 3000)
end
specify "he should be able to see his kms and today stats", js: true do
# This spec checks that 3 km appear when clicked on "show today stats" button
end
specify "he should be able to see his kms and week stats", js: true do
# This spec checks that 3+5 km appear when clicked on "show weekly stats" button
end
end
end
def stub_track(time, user_id, meters)
# Fabricates a track using DateTime.now-(3.days/1.week/2.months) with the provided
# user_id and provided meters
end
I have a page that creates a snapshot of a document. That document is saved with the title being a timestamp (September 27, 2014 at 4:01:10 pm) for example. I am writing a test for this page and want to stub time so that it doesn't change.
What I have at the moment is Time.stubs(:now).returns(Time.parse("2014-1-2 11:00:00")) but when I do that I get an error message saying:
Capybara::FrozenInTime: time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead
What is the best way to stub out time here?
Rails now includes support for time travel directly, e.g.:
feature 'Time travel verifier' do
include ActiveSupport::Testing::TimeHelpers
scenario 'works in the past' do
travel_to(1.day.ago) do
visit time_travel_verification_path
expect(page).to have_content('WOAH Time Travel!')
end
end
end
I'm reposting here my comment as an answer.
There is the timecop gem https://github.com/travisjeffery/timecop
It allows you to do things like the following:
describe "some set of tests to mock" do
before do
Timecop.freeze(Time.local(1990))
end
after do
Timecop.return
end
it "should do blah blah blah" {}
end
which will make the tests run as if it was 1990-1-1 and then return back to the current time.
I'm trying to use fixtures to add more complex test data in order to test specific scenarios with the front-end, which is in Flex. I'm not sure this is the right way to go about it with rails. My rails app is a card game and the 'more complex test data' I'm trying to test are various combinations of cards.
For example, I want to set up a test game where player 1 has cards B and C in hand, where I've specifically added cards B and C to the player's hand in a fixture.
I have basic fixtures for players, games, and users, which have been there for awhile and working fine. I've tried to add the following erb code in the games fixture, to invoke the Game.start method, and am getting
NoMethodError: undefined method `games' for main:Object
The fixture code snippet is :
four:
id: 4
num_players: 3
turn_num: 0
status_id: 1
<% game_four = games(:four).find
game_four.start
%>
game_four = games(:four).find
games method exists only in test cases, not in fixtures.
You should either query the database or use relationships.
This is just an example.
four:
id: 4
num_players: 3
turn_num: 0
status_id: 1
<% Game.find_by_name(four).start %>
Also, this is not really the right place for such command. Fixtures are not intended "to start games".
You should really move this command elsewhere, perhaps in a dedicated test case within the setup block.
EDIT:
I copy here my comment posted a couple of days ago on the original answer with a link to the new Rails Database Seeding feature: http://ryandaigle.com/articles/2009/5/13/what-s-new-in-edge-rails-database-seeding
This is the one explained by Yehuda Katz in his answer and definitely the best way to solve this problem.
Probably the best solution (and in fact, the one that is now canonized on edge) is to have a seeds.rb file in your db directory that you load from a rake task.
Here's what Rails does now on edge (to be in Rails 3).
# db/seeds.rb
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
# Major.create(:name => 'Daley', :city => cities.first)
And then a new rake task (which you can add to your Rakefile):
desc 'Load the seed data from db/seeds.rb'
task :seed => :environment do
seed_file = File.join(Rails.root, 'db', 'seeds.rb')
load(seed_file) if File.exist?(seed_file)
end
If you set up your seeds.rb file this way, you will be following the new convention and will be able to delete the seeds rake task when you upgrade to Rails 3.
Also, migrations are not for data. This is well established and the universal opinion of the Rails core team as far as I know.
If you want to use fixtures method (when loading data for development, not during tests) you can use fixtures_references plugin. Its behaviour will be the same.