简体   繁体   中英

Rails Rspec test fails when it works in app

I have the following code and test that I can't seem to make pass. The code should be auto locking all bookings that have been completed over 24 hours ago.

When I put a pry into the test and run the first line of Booking.auto_lock_guests nothing happens. When I type booking_7 and after type Booking.auto_lock_guests then it changes locked to true. Is this something to do with the way let is set up that it is not showing up in Booking.all? Or is it the way I have written the test?

Any help would be greatly appreciated.

  def self.auto_lock_guests
    bookings = Booking.where(guests_completed: true, locked: false)
    bookings.each do |booking|
      next unless booking.guests_completed_at <= 1.day.ago
      booking.locked = true
      booking.save
    end
  end


  context 'auto_lock_guests' do
    let(:booking_6) { FactoryGirl.create(:booking, date: Date.today - 5.day, guests_completed: true, guests_completed_at: DateTime.now, locked: false )}
    let(:booking_7) { FactoryGirl.create(:booking, date: Date.today - 5.day, guests_completed: true, guests_completed_at: DateTime.now - 3.day, locked: false )}
    before do
      Booking.auto_lock_guests
    end
    it 'should only lock bookings with a guests_completed date older than a day ago' do
      expect(booking_7.locked).to eq(true)
      expect(booking_6.locked).to eq(false)
    end
  end

let is lazily evaluated. When the before block is executed there are no records, because the let blocks haven't yet been called.

Either change let to let! to execute the block immediately or call booking_6 and booking_7 right before Booking.auto_lock_guests

EDIT:

Also you don't check wether the booking.save succeeded. If booking.save failed - you would never know. :)

The next unless booking.guests_completed_at <= 1.day.ago could probably be rewritten as a query: where(Booking.arel_table[:guests_completed_at].gt(1.day.ago))

You don't need to iterate through the records in the first place. In fact it will cause problems as your app scales since pulling all those records into memory will exhaust the servers (or dynos) memory.

You can select the records from the database and update them in a single query:

class Booking
  def self.auto_lock_guests!
    bookings = Booking.where(guests_completed: true, locked: false)
                      .where('guests_completed_at <= ?', 1.day.ago)
    bookings.update_all(locked: true)
  end
end

The difference in execution time between many individual UPDATE queries and an updating many rows at once can be massive.

To test it you can create multiple records and use change expectations:

# use describe and not context for methods.
describe ".auto_lock_guests" do
  # let! is not lazy loading
  let!(:old_booking) { FactoryGirl.create(:booking, date: 7.days.ago, guests_completed: true, guests_completed_at: 3.days.ago, locked: false )}
  let!(:new_booking) { FactoryGirl.create(:booking, date: Date.today, guests_completed: true, guests_completed_at: DateTime.now, locked: false )}

   it 'locks a booking with a guests_completed date older than a day ago' do
     expect do
       Bookings.auto_lock_guests! && old_booking.reload
     end.to change { old_booking.locked }.from(false).to(true)
   end

   it 'does not lock a when guests_completed date is less than a day ago' do
     expect do
       Bookings.auto_lock_guests! && new_booking.reload
     end.to_not change { new_booking.locked }.from(false).to(true)
   end
end

Using change is a very good idea when testing methods that change the database as they verify both the initial state and the result.

I ended up having to add this into the before action after calling Booking.auto_lock_guests and it worked.

before do
  Booking.auto_lock_guests
  booking_7.reload
  booking_6.reload
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM