Skip to content

Commit 3f44835

Browse files
committed
fix(test): freeze time in tests to prevent flaky failures
Remove timecop gem dependency and use Rails' travel_to helper. Time-based scopes compare against Time.zone.now evaluated at query time, while test fixtures used relative times evaluated at creation time. This caused flaky tests when any time passed between fixture creation and query. Files changed: - Remove timecop from Gemfile and test.rb config - Add travel_to blocks to time-dependent tests in shared examples, listable_spec, attendance_warning_spec, feature tests, rake tasks, and presenter specs Fixes flaky test failures in CI runs.
1 parent 10bf440 commit 3f44835

15 files changed

Lines changed: 278 additions & 220 deletions

Gemfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ group :test do
114114
gem 'shoulda-matchers', '~> 7.0'
115115
gem 'simplecov', require: false
116116
gem 'simplecov-lcov', require: false
117-
gem 'timecop', '~> 0.9.10'
118117
gem 'webmock'
119118
end
120119

Gemfile.lock

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,6 @@ GEM
573573
execjs (>= 0.3.0, < 3)
574574
thor (1.5.0)
575575
tilt (2.7.0)
576-
timecop (0.9.10)
577576
timeout (0.6.0)
578577
tsort (0.2.0)
579578
turbo-rails (2.0.23)
@@ -700,7 +699,6 @@ DEPENDENCIES
700699
stimulus-rails
701700
stripe
702701
terser
703-
timecop (~> 0.9.10)
704702
turbo-rails
705703
tzinfo-data
706704
view_component

config/environments/test.rb

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
require "active_support/core_ext/integer/time"
2-
require "timecop"
1+
require 'active_support/core_ext/integer/time'
32

43
# The test environment is used exclusively to run your application's
54
# test suite. You never need to work with it otherwise. Remember that
@@ -16,10 +15,10 @@
1615
# this is usually not necessary, and can slow down your test suite. However, it's
1716
# recommended that you enable it in continuous integration systems to ensure eager
1817
# loading is working properly before deploying your code.
19-
config.eager_load = ENV["CI"].present?
18+
config.eager_load = ENV['CI'].present?
2019

2120
# Configure public file server for tests with cache-control for performance.
22-
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.hour.to_i}" }
21+
config.public_file_server.headers = { 'cache-control' => "public, max-age=#{1.hour.to_i}" }
2322

2423
# Show full error reports.
2524
config.consider_all_requests_local = true
@@ -41,7 +40,7 @@
4140
config.action_mailer.delivery_method = :test
4241

4342
# Set host to be used by links generated in mailer templates.
44-
config.action_mailer.default_url_options = { host: "localhost:3000" }
43+
config.action_mailer.default_url_options = { host: 'localhost:3000' }
4544

4645
# Print deprecation notices to the stderr.
4746
config.active_support.deprecation = :stderr
@@ -63,7 +62,4 @@
6362
Bullet.bullet_logger = true
6463
Bullet.raise = false # raise an error if n+1 query occurs
6564
end
66-
67-
# https://github.com/travisjeffery/timecop#timecopsafe_mode
68-
Timecop.safe_mode = true
6965
end

spec/features/admin/announcements_spec.rb

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
click_on 'announcement[create]'
1616

1717
expect(page).to have_content('Announcement successfully created')
18-
expect(page.current_path).to eq(admin_announcements_path)
18+
expect(page).to have_current_path(admin_announcements_path, ignore_query: true)
1919
end
2020
end
2121

@@ -37,18 +37,20 @@
3737

3838
expect(page).to have_content('Announcement successfully updated')
3939
expect(page).to have_content('New event coming up soon! Stay tuned.')
40-
expect(page.current_path).to eq(admin_announcements_path)
40+
expect(page).to have_current_path(admin_announcements_path, ignore_query: true)
4141
end
4242
end
4343

4444
scenario 'can view all announcements' do
45-
announcement = Fabricate(:announcement)
46-
old_announcement = Fabricate(:announcement, expires_at: Time.zone.now - 1.week)
45+
travel_to(Time.current) do
46+
announcement = Fabricate(:announcement)
47+
old_announcement = Fabricate(:announcement, expires_at: 1.week.ago)
4748

48-
visit admin_announcements_path
49+
visit admin_announcements_path
4950

50-
expect(page).to have_content(announcement.message)
51-
expect(page).to have_content(old_announcement.message)
51+
expect(page).to have_content(announcement.message)
52+
expect(page).to have_content(old_announcement.message)
53+
end
5254
end
5355

5456
scenario 'can successfully send a new announcement to every group' do
@@ -60,7 +62,7 @@
6062
expect(page).to have_content('An announcement to every group')
6163
expect(page).to have_content("Coaches #{chapter.name}")
6264
expect(page).to have_content("Students #{chapter.name}")
63-
expect(page.current_path).to eq(admin_announcements_path)
65+
expect(page).to have_current_path(admin_announcements_path, ignore_query: true)
6466
end
6567

6668
scenario 'can successfully send a new announcement to selected groups' do
@@ -71,8 +73,8 @@
7173

7274
expect(page).to have_content('An announcement to selected groups')
7375
expect(page).to have_content("Coaches #{chapter.name}")
74-
expect(page).to_not have_content("Students #{chapter.name}")
75-
expect(page.current_path).to eq(admin_announcements_path)
76+
expect(page).not_to have_content("Students #{chapter.name}")
77+
expect(page).to have_current_path(admin_announcements_path, ignore_query: true)
7678
end
7779
end
7880
end

spec/features/chapter_spec.rb

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
end
1010

1111
it 'a visitor to the website can access inactive chapter events' do
12-
past_workshop = Fabricate(:workshop, chapter: inactive_chapter, date_and_time: Time.zone.today - 2.weeks)
12+
travel_to(Time.current) do
13+
past_workshop = Fabricate(:workshop, chapter: inactive_chapter, date_and_time: 2.weeks.ago)
1314

14-
visit workshop_path(past_workshop)
15+
visit workshop_path(past_workshop)
1516

16-
expect(page).to have_content "Workshop at #{past_workshop.host.name}"
17+
expect(page).to have_content "Workshop at #{past_workshop.host.name}"
18+
end
1719
end
1820
end
1921

@@ -25,59 +27,67 @@
2527
end
2628

2729
it 'renders chapter without organisers' do
28-
chapter = Fabricate(:chapter_without_organisers, name: "Empty Chapter")
30+
chapter = Fabricate(:chapter_without_organisers, name: 'Empty Chapter')
2931
expect(chapter.organisers.size).to eq 0
3032

3133
visit chapter_path(chapter.slug)
3234

33-
expect(page).to have_content "Empty Chapter"
34-
expect(page).not_to have_content "Team"
35+
expect(page).to have_content 'Empty Chapter'
36+
expect(page).not_to have_content 'Team'
3537
end
3638

3739
it 'renders any upcoming workshops for the chapter' do
38-
chapter = Fabricate(:chapter)
39-
workshops = 2.times.map do |n|
40-
Fabricate(:workshop, chapter: chapter, date_and_time: Time.zone.now + 9.days - n.weeks)
41-
end
42-
43-
visit chapter_path(chapter.slug)
44-
workshops.each do |workshop|
45-
expect(page).to have_content "Workshop at #{workshop.host.name}"
40+
travel_to(Time.current) do
41+
chapter = Fabricate(:chapter)
42+
workshops = 2.times.map do |n|
43+
Fabricate(:workshop, chapter: chapter, date_and_time: 9.days.from_now - n.weeks)
44+
end
45+
46+
visit chapter_path(chapter.slug)
47+
workshops.each do |workshop|
48+
expect(page).to have_content "Workshop at #{workshop.host.name}"
49+
end
4650
end
4751
end
4852

4953
it 'renders any upcoming events for the chapter' do
50-
chapter = Fabricate(:chapter)
51-
2.times.map do |n|
52-
Fabricate(:event, name: "Event #{n + 1}",
53-
chapters: [chapter],
54-
date_and_time: Time.zone.now + 2.months - n.months)
54+
travel_to(Time.current) do
55+
chapter = Fabricate(:chapter)
56+
2.times.map do |n|
57+
Fabricate(:event, name: "Event #{n + 1}",
58+
chapters: [chapter],
59+
date_and_time: 2.months.from_now - n.months)
60+
end
61+
62+
visit chapter_path(chapter.slug)
63+
expect(page).to have_content 'Event 1'
64+
expect(page).to have_content 'Event 2'
5565
end
56-
57-
visit chapter_path(chapter.slug)
58-
expect(page).to have_content 'Event 1'
59-
expect(page).to have_content 'Event 2'
6066
end
6167

6268
it 'renders the most recent past workshop for the chapter' do
63-
chapter = Fabricate(:chapter)
64-
past_workshop = Fabricate(:workshop, chapter: chapter, date_and_time: Time.zone.today - 2.weeks)
65-
recent_past_workshop = Fabricate(:workshop, chapter: chapter, date_and_time: Time.zone.today - 1.week)
66-
67-
visit chapter_path(chapter.slug)
68-
expect(page).to have_content "Workshop at #{recent_past_workshop.host.name}"
69-
expect(page).to_not have_content "Workshop at #{past_workshop.host.name}"
69+
travel_to(Time.current) do
70+
chapter = Fabricate(:chapter)
71+
past_workshop = Fabricate(:workshop, chapter: chapter, date_and_time: 2.weeks.ago)
72+
recent_past_workshop = Fabricate(:workshop, chapter: chapter, date_and_time: 1.week.ago)
73+
74+
visit chapter_path(chapter.slug)
75+
expect(page).to have_content "Workshop at #{recent_past_workshop.host.name}"
76+
expect(page).not_to have_content "Workshop at #{past_workshop.host.name}"
77+
end
7078
end
7179

7280
it 'renders the 6 most recent sponsors for the chapter' do
73-
chapter = Fabricate(:chapter)
74-
workshops = 2.times.map do |n|
75-
Fabricate(:workshop, chapter: chapter, date_and_time: Time.zone.now - n.weeks)
76-
end
77-
78-
visit chapter_path(chapter.slug)
79-
workshops.each do |workshop|
80-
expect(page).to have_link(workshop.sponsors.name)
81+
travel_to(Time.current) do
82+
chapter = Fabricate(:chapter)
83+
workshops = 2.times.map do |n|
84+
Fabricate(:workshop, chapter: chapter, date_and_time: n.weeks.ago)
85+
end
86+
87+
visit chapter_path(chapter.slug)
88+
workshops.each do |workshop|
89+
expect(page).to have_link(workshop.sponsors.name)
90+
end
8191
end
8292
end
8393
end

spec/features/listing_coaches_spec.rb

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,37 @@
66
end
77

88
scenario 'I can see the top coaches by year' do
9-
latest_workshop = Fabricate(:workshop, date_and_time: Time.zone.now - 1.year)
10-
old_workshop = Fabricate(:workshop, date_and_time: Time.zone.now - 3.years)
11-
invitations = 2.times { Fabricate(:attended_coach, workshop: latest_workshop) }
12-
older_invitations = 4.times { Fabricate(:attended_coach, workshop: old_workshop) }
9+
travel_to(Time.current) do
10+
latest_workshop = Fabricate(:workshop, date_and_time: 1.year.ago)
11+
old_workshop = Fabricate(:workshop, date_and_time: 3.years.ago)
12+
2.times { Fabricate(:attended_coach, workshop: latest_workshop) }
13+
4.times { Fabricate(:attended_coach, workshop: old_workshop) }
1314

14-
visit coaches_path(year: latest_workshop.date_and_time.year)
15-
expect(page).to have_css(".coach", count: 2)
15+
visit coaches_path(year: latest_workshop.date_and_time.year)
16+
expect(page).to have_css('.coach', count: 2)
1617

17-
visit coaches_path(year: old_workshop.date_and_time.year)
18-
expect(page).to have_css(".coach", count: 4)
18+
visit coaches_path(year: old_workshop.date_and_time.year)
19+
expect(page).to have_css('.coach', count: 4)
20+
end
1921
end
2022

2123
scenario 'I can navigate the top coaches by year' do
22-
current_workshop = Fabricate(:workshop, date_and_time: Time.zone.now)
23-
latest_workshop = Fabricate(:workshop, date_and_time: Time.zone.now - 1.year)
24-
old_workshop = Fabricate(:workshop, date_and_time: Time.zone.now - 3.years)
25-
current_invitations = 1.times { Fabricate(:attended_coach, workshop: current_workshop) }
26-
invitations = 3.times { Fabricate(:attended_coach, workshop: latest_workshop) }
27-
older_invitations = 2.times { Fabricate(:attended_coach, workshop: old_workshop) }
24+
travel_to(Time.current) do
25+
current_workshop = Fabricate(:workshop, date_and_time: Time.current)
26+
latest_workshop = Fabricate(:workshop, date_and_time: 1.year.ago)
27+
old_workshop = Fabricate(:workshop, date_and_time: 3.years.ago)
28+
1.times { Fabricate(:attended_coach, workshop: current_workshop) }
29+
3.times { Fabricate(:attended_coach, workshop: latest_workshop) }
30+
2.times { Fabricate(:attended_coach, workshop: old_workshop) }
2831

29-
visit coaches_path
30-
expect(page).to have_css(".coach", count: 1)
32+
visit coaches_path
33+
expect(page).to have_css('.coach', count: 1)
3134

32-
click_on latest_workshop.date_and_time.year.to_s
33-
expect(page).to have_css(".coach", count: 3)
35+
click_on latest_workshop.date_and_time.year.to_s
36+
expect(page).to have_css('.coach', count: 3)
3437

35-
click_on old_workshop.date_and_time.year.to_s
36-
expect(page).to have_css(".coach", count: 2)
38+
click_on old_workshop.date_and_time.year.to_s
39+
expect(page).to have_css('.coach', count: 2)
40+
end
3741
end
3842
end

spec/features/listing_events_spec.rb

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@
55
let!(:event) { Fabricate(:event) }
66

77
scenario 'displays upcoming events page' do
8-
visit upcoming_events_path
9-
expect(page).to have_content 'Upcoming Events'
10-
expect(page).to have_content event.name
8+
travel_to(Time.current) do
9+
visit upcoming_events_path
10+
expect(page).to have_content 'Upcoming Events'
11+
expect(page).to have_content event.name
12+
end
1113
end
1214
end
1315

1416
describe 'I can see past events' do
1517
let!(:chapter) { Fabricate(:chapter, active: true) }
16-
let!(:past_event) { Fabricate(:event, date_and_time: Time.zone.now - 2.weeks) }
17-
let!(:past_workshop) { Fabricate(:workshop, date_and_time: Time.zone.now - 1.week, chapter: chapter) }
18+
let!(:past_event) { Fabricate(:event, date_and_time: 2.weeks.ago) }
19+
let!(:past_workshop) { Fabricate(:workshop, date_and_time: 1.week.ago, chapter: chapter) }
1820

1921
scenario 'displays past events page' do
20-
visit past_events_path
21-
expect(page).to have_content 'Past Events'
22-
expect(page).to have_content past_event.name
22+
travel_to(Time.current) do
23+
visit past_events_path
24+
expect(page).to have_content 'Past Events'
25+
expect(page).to have_content past_event.name
26+
end
2327
end
2428
end
2529

@@ -33,26 +37,32 @@
3337

3438
context 'pagination' do
3539
scenario 'past events paginates at 20 per page' do
36-
chapter = Fabricate(:chapter, active: true)
37-
Fabricate.times(22, :event, date_and_time: 2.weeks.ago)
38-
Fabricate(:workshop, date_and_time: 3.weeks.ago, chapter: chapter)
40+
travel_to(Time.current) do
41+
chapter = Fabricate(:chapter, active: true)
42+
Fabricate.times(22, :event, date_and_time: 2.weeks.ago)
43+
Fabricate(:workshop, date_and_time: 3.weeks.ago, chapter: chapter)
3944

40-
visit past_events_path
41-
expect(page).to have_selector('.card', count: 20)
45+
visit past_events_path
46+
expect(page).to have_selector('.card', count: 20)
47+
end
4248
end
4349

4450
scenario 'past meetings paginate at 20 per page' do
45-
Fabricate.times(22, :meeting, date_and_time: 2.weeks.ago)
51+
travel_to(Time.current) do
52+
Fabricate.times(22, :meeting, date_and_time: 2.weeks.ago)
4653

47-
visit past_events_path
48-
expect(page).to have_selector('.card', count: 20)
54+
visit past_events_path
55+
expect(page).to have_selector('.card', count: 20)
56+
end
4957
end
5058

5159
scenario 'upcoming meetings paginate at 20 per page' do
52-
Fabricate.times(22, :meeting, date_and_time: 2.weeks.from_now)
60+
travel_to(Time.current) do
61+
Fabricate.times(22, :meeting, date_and_time: 2.weeks.from_now)
5362

54-
visit upcoming_events_path
55-
expect(page).to have_selector('.card', count: 20)
63+
visit upcoming_events_path
64+
expect(page).to have_selector('.card', count: 20)
65+
end
5666
end
5767
end
5868
end

0 commit comments

Comments
 (0)