Ruby Testing Micro-Course, Lesson 4

by Jason Swett,

Lesson 1 / Lesson 2 / Lesson 3 / Lesson 4

Review of Lesson 3

In Lesson 3 we added the concept of success or failure to the `check_in_guest` method.

Then I gave you the chance to write a test to ensure that when a guest is checked out the guest’s room gets freed up.

What We’ll Do In Lesson 4

This final lesson will be a short one. I’m going to show you what my test looks like for checking out a guest. Then I’ll give you a suggestion for work you can continue on your own.

Here’s the test I wrote for freeing up a room:

it 'frees up the room' do
  hotel.check_in_guest('Roy Orbison', 302)
  hotel.check_out_guest('Roy Orbison')
  expect(hotel.check_in_guest('George Harrison', 302)).to be true
end

I’m checking Roy Orbison into room 302, then checking him out. Then I’m saying that if I try to check George Harrison into room 302, the check-in should be successful.

If we run the tests, this new test will fail. I’ll show you that in a second. First here’s my test in the context of the full test suite.

describe Hotel do
  let(:hotel) { Hotel.new }

  describe 'checking in a guest' do
    context 'room is available' do
      it 'allows check-in' do
        expect(hotel.check_in_guest('George Harrison', 302)).to be true
      end

      it "adds the guest to the hotel's guest list" do
        hotel.check_in_guest('George Harrison', 302)
        expect(hotel.guests).to include 'George Harrison'
      end
    end

    context 'room is not available' do
      it 'disallows check-in' do
        hotel.check_in_guest('Roy Orbison', 302)
        expect(hotel.check_in_guest('George Harrison', 302)).to be false
      end

      it "does not add the guest to the hotel's guest list" do
        hotel.check_in_guest('Roy Orbison', 302)
        hotel.check_in_guest('George Harrison', 302)
        expect(hotel.guests).not_to include 'George Harrison'
      end
    end
  end

  describe 'checking out a guest' do
    it "removes the guest from the hotel's guest list" do
      hotel.check_in_guest('Buddy Holly', 303)
      hotel.check_out_guest('Buddy Holly')
      expect(hotel.guests).not_to include 'Buddy Holly'
    end

    it 'frees up the room' do
      hotel.check_in_guest('Roy Orbison', 302)
      hotel.check_out_guest('Roy Orbison')
      expect(hotel.check_in_guest('George Harrison', 302)).to be true
    end
  end
end

And like I said above, if we try to run this, the new test will fail. We can’t check a new guest into the room because currently our `check_out_guest` method doesn’t do anything to free up the room.

$ rspec hotelier_spec.rb 
.....F

Failures:

  1) Hotel checking out a guest frees up the room
     Failure/Error: expect(hotel.check_in_guest('George Harrison', 302)).to be true
     
       expected true
            got false
     # ./hotelier_spec.rb:62:in `block (3 levels) in <top (required)>'

Finished in 0.02631 seconds (files took 0.14313 seconds to load)
6 examples, 1 failure

Failed examples:

rspec ./hotelier_spec.rb:59 # Hotel checking out a guest frees up the room

What does it mean to free up a room, exactly? Well, the place we keep our list of occupied rooms is in the (appropriately-named) `@occupied_rooms` instance variable. To free up room 302, we’d need to remove 302 from `@occupied_rooms`.

So we’ll add `@occupied_rooms.delete(room_number)` to `check_out_guest`. The `check_out_guest` method doesn’t know anything about a `room_number` right now, so we’ll also have to modify the method to take a `room_number` argument.

Since we modified the function signature for `check_out_guest`, we’ll also have to change all the instances where it’s used. All these changes are highlighted below.

require 'rspec'

class Hotel
  attr_accessor :guests

  def initialize
    @guests = []
    @occupied_rooms = []
  end

  def check_in_guest(guest_name, room_number)
    return false if @occupied_rooms.include?(room_number)
    @guests << guest_name
    @occupied_rooms << room_number
    true
  end

  def check_out_guest(guest_name, room_number)
    @guests.delete(guest_name)
    @occupied_rooms.delete(room_number)
  end
end

describe Hotel do
  let(:hotel) { Hotel.new }

  describe 'checking in a guest' do
    context 'room is available' do
      it 'allows check-in' do
        expect(hotel.check_in_guest('George Harrison', 302)).to be true
      end

      it "adds the guest to the hotel's guest list" do
        hotel.check_in_guest('George Harrison', 302)
        expect(hotel.guests).to include 'George Harrison'
      end
    end

    context 'room is not available' do
      it 'disallows check-in' do
        hotel.check_in_guest('Roy Orbison', 302)
        expect(hotel.check_in_guest('George Harrison', 302)).to be false
      end

      it "does not add the guest to the hotel's guest list" do
        hotel.check_in_guest('Roy Orbison', 302)
        hotel.check_in_guest('George Harrison', 302)
        expect(hotel.guests).not_to include 'George Harrison'
      end
    end
  end

  describe 'checking out a guest' do
    it "removes the guest from the hotel's guest list" do
      hotel.check_in_guest('Buddy Holly', 303)
      hotel.check_out_guest('Buddy Holly', 303)
      expect(hotel.guests).not_to include 'Buddy Holly'
    end

    it 'frees up the room' do
      hotel.check_in_guest('Roy Orbison', 302)
      hotel.check_out_guest('Roy Orbison', 302)
      expect(hotel.check_in_guest('George Harrison', 302)).to be true
    end
  end
end

Now, if we run our test suite, everything passes.

$ rspec hotelier_spec.rb
......

Finished in 0.00723 seconds (files took 0.08605 seconds to load)
6 examples, 0 failures

What Now?

Congratulations on completing this micro-course. If you’d like to keep going, here are some ideas for what you can do next.

Separate the test suite from the application code. In this course I put everything in one file for simplicity and ease. That’s not what you’d do for a production application though. Try to separate the application code and the test suite into two different files.

Keep track of room/guest association. Right now we’re not keeping explicit track of which guests are in which rooms. There’s nothing preventing us from, say, checking Roy Orbison into room 302 and then checking him out of room 563. It also feels unnatural that we have to specify a room number when checking out a guest. One would expect the application to keep track of that. See if you can make this happen.

Add payments. What if checking a guest out required the guest to pay? You could also add some simple reporting capabilities. How much revenue has the hotel earned?

Add dates. It would make sense to keep track of the dates when guests check in and check out. If combined with payments and reporting, this would give you the ability to show the hotel’s revenue for a certain time period.

Conclusion

Thanks for taking the time to learn about Ruby testing. If you have any questions, I usually suggest using Stack Overflow, but you’re also welcome to leave a question in the comments.

If you made it this far, I’d encourage you to use the link below to get this full micro-course via email so you have it forever.

2 thoughts on “Ruby Testing Micro-Course, Lesson 4

Leave a Reply

Your email address will not be published. Required fields are marked *