Lesson Weekend

In order to test our backend logic, we will be adding some new concepts to our tests. These concepts aren't just relevant now — we'll also use them in the next section when we start working with databases. Let's go over these concepts before we delve more deeply into our backend logic. Note: You don't need to add any of this code to your application yet as we are still covering key concepts. We will add all necessary methods to our code soon.

Naming Convention for Testing Class Methods


We'll name our class and instance methods slightly differently in our tests:

album_spec.rb
describe '#Album' do
  describe('.all') do
    # Note that the class method all() is preceded by a .
  end

  describe('#save') do
    # Note that the instance method save() is preceded by a #.
  end
end
...

When testing Ruby code, class methods are preceded by a . and instance methods begin with #. This makes it easier for other developers (and us) to quickly take a look at our code and see what we're testing.

Overriding the Equality Operator for Testing


Sometimes we'll need to check and see if two objects are "equal" to each other, especially when we are writing tests. For instance, if two albums have the same name, artist, year, and so on then they should be the same album. However, it's not quite that simple. Let's go into IRB and add the following code:

class Album
  def initialize(name)
    @name = name
  end
end

This just allows us to create Albums with a name. Now let's compare two Albums that have the same name:

> album = Album.new("Blue")
=> #<Album:0x007f9b9132ed38 @name="Blue">
> irb(main):007:0> album2 = Album.new("Blue")
=> #<Album:0x007f9b91314208 @name="Blue">
> irb(main):008:0> album == album2
=> false

Because they are different objects, Ruby does not recognize that they are supposed to be the same albums. In order to fix this for our tests, we need to override the equality operator ==. We should always be very careful when overriding Ruby's built-in methods; it's usually not a good idea because it can lead to unexpected behavior, but in this case, we genuinely need it for our tests.

Here's a method that fixes this issue for RSpec:

album.rb
class Album
  attr_reader :name
  ...
  def ==(other_album)
    self.name.eql?(other_album.name)
  end
end

Here we use the eql? method to check if both Albums have the same name. If they do, the == operator will return true. Try it out in IRB. This method needs to be modified and expanded for each new attribute. For instance, if an Album has a name, artist, and year, our method would look like this:

album.rb
def ==(other_album)
  self.name.eql?(other_album.name) && self.artist.eql?(other_album.artist) && self.year.eql?(other_album.year)
end

For the basic application we build in this section, we'll keep it very simple and Albums will only have a name attribute. If you end up building out the application further (and the same applies for any other application you work on), you'll have to modify this method as you add more attributes to the class.

Clearing Our Mock Database Before Each Test


If our application had a test database, we'd want to clear it out between each test. Even though we don't have a test database yet, class variables do have persistence. This means they might change from test to test unless we clear their values. This could cause either false positives or negatives in our tests.

RSpec offers before and after blocks to clear or otherwise prepare the test environment between tests. We've already learned about using these kinds of blocks to set up and clean up tests with Jest. The syntax in RSpec looks like this:

before(:each) do
  # Code to set up environment between tests goes here. 
  # We will create an Album.clear() method in the next lesson 
  # to empty our mock database before each test.
end

Before and after blocks are very common and we will use them regularly.

Using Instance Variables to DRY Up Tests


We can also use instance variables in our before(:each) blocks like this:

album.rb
before(:each) do
  @album = Album.new( #create a new album )
end

This instance variable will be available to all of our tests. This can DRY up tests considerably, particularly if we are creating the same object in multiple tests.

If any of the concepts covered in this lesson are confusing, don't worry. We'll be incorporating these concepts in our code. You can always use this lesson as a reference as needed.

We're now ready to write and test our backend logic!

Naming Convention for Testing Class Methods


We name class and instance methods slightly differently in our tests:

describe '#Album' do
  describe('.all') do
    # Note that the class method all() is preceded by a .
  end

  describe('#save') do
    # Note that the instance method save() is preceded by a #.
  end
end
...

Overriding the Equality Operator for Testing


A sample method for overriding the equality operator for tests. This method should always compare all properties.

class Album
  attr_reader :name
  ...
  def ==(other_album)
    self.name.eql?(other_album.name)
  end
end

Clearing the Database Before Tests


before(:each) do
  # Code to set up environment between tests goes here. We will create an Album.clear() method in the next lesson to empty our mock database before each test.
end

Using Instance Variables to DRY Up Tests


We can also use instance variables in our before(:each) blocks like this:

before(:each) do
  @album = Album.new( #create a new album )
end

Lesson 10 of 37
Last updated August 7, 2022