Testing With RSpec

Tom Tobar
The Startup
Published in
8 min readAug 30, 2020

First, a little on the subject of Test-Driven Development or TDD. It is a recursive process by which we write code, test code, and repeat. In the words of Kent Beck, author of “Test-Driven Development: By Example”, it “encourages simple designs and inspires confidence.”

RSpec is communicative when it gives you feedback after you process the code you wrote against the tests. If configured for colour (which should be default in my opinion), it shows if the tested code fails in(red) or passes in (green) along with a descriptive output. It can tell you all the problems that need work or can hone in on one problem at a time to keep you focused. Seeing a green output in the terminal is like having a dopamine button at your fingertips and can be very addicting but in a good way.

This is the green that you will learn to love! 😍

What is RSepc?

RSpec is a Testing Framework written in the Ruby programming language. The “R” in RSpec stands for “Ruby” and “Spec” means “Specification”. It uses a unique Domain Specific Language (DSL) that is meant to read like a normal conversation. If you are not familiar with RSpec and you read a test, it will probably make sense to you.

Getting Started With RSpec 🐎

Since RSpec is written in the Ruby Programming Language, we first need to start with checking the version that is installed on your computer. In the terminal, run this command:

ruby -v

You should take the time to upgrade your Ruby version if it’s not 2.4 or higher. RSpec will work with older versions of Ruby but there have been improvements to Ruby that make it faster and that means faster testing with RSpec.

You can check to see if you already have RSpec installed on your system using the command:

rspec --version

If you do make sure that you have the latest and greatest from RSpec by visiting the website. <== Click me!

Installing RSpec

RSpec is a collection of Ruby gems that work together to give us this wonderful capability. RSpec itself is a gem and installs just like all other Ruby Gems:

gem install rspec

RSpec help

This command will give you some handy info on RSpec.

rspec --help

I have this copied and pasted in my Evernote notes. The output section here is particularly helpful for formatting your output so it’s easier to read in the terminal. You can configure RSpec on your machine to make output changes permanent by creating a ~/.rspec file in your root directory and adding the flag(s) that you want RSpec to fire on each call.

RSpec lets you run one test at a time using the ‘fail fast’ flag. This information can be found at the top under:

rspec spec/file_name.rb --fail-fast#the --fail-fast flag can also be reduced to:--f-f

Initializing RSpec Within a Directory

In a new project directory, use the command:

rspec --init

This will generate an .rspec file, a spec directory, and a file inside that directory called spec_helper.rb.

Creating a Test File

When naming your test files, it is convention to take the corresponding name of the file that contains the tested code and add _spec.rb to the end of it. Here is a file tree example.

cat_spec.rb named after the cat.rb file in lib dir

In the top most level of the project directory lives the lib directory, spec directory and the .rspec file. Just ignore that spec_helper.rb file for now. The cat.rb file in the lib directory is where we will be writing the code that we want to test. Cat_spec.rb is where we will writing all of the tests for cat.rb so we need to make a connection to that file by requiring it in our cat_spec.rb file:

🤔Thinking about what we are testing

Considering what behavior you would like to see from the code you are testing is a critical step. At this point we can ask questions like “What attributes should my cat class have?” or “What should an instance of my cat class respond to?” Questions like this can easily be converted into RSpec DSL and be structured in a very similar way. There are many testing cases that we can account for but overall functionality of what we want to see a particular function produce is the most important.

Does the thing successfully do what you want it to do?

Other cases to consider:

What could go wrong? How do we handle a bad input?

We should consider these questions so that our program can gracefully handle these scenarios and users can still have a great experience. We want to test for all possible scenarios but there are some that just will not be that common. Writing tests for these edge cases is important but not on the same level as testing for core functionality.

Things that we don’t want to test

Let just all agree that the Ruby Programming Language, in its 25 years of existence (as of 2020), has been well maintained and is functioning as it was intended. Given that, we do not need to make sure that it is doing what it’s supposed to. In the same vein, Rails functionality and third party API’s should not need to be tested for functionality.

Additionally, we don’t want to double test something in our code. If you test functionality in one place and another part of your project utilizes that same code, you don’t need to make sure that it is doing the same thing in a different location.

🗣Speaking RSpec

We start off with describe.

Describe is an RSpec method that takes an argument of a string or class name and is used to define an example group. In the above example, I passed in a class name to the describe method but it could have been a string as well. Heres another way to look at it:

The second example is functionally the same as the first but the beauty of RSpec’s DSL is that when structured like the first example, it is easier to read and that is key here. The tests can be used to communicate with non technical persons to clarify what is happening so having well structured tests will serve more than one purpose.

‘It’, not the clown…🤡

Let’s say that I want my Cat instance to be initialized with a name.

Inside of the example block, the ‘it’ is used to define an example. This clever use of ‘it’ allows us to read the test intuitively. The main parts that might catch your eye here are “Describe Cat — it is initialized with a name — I expect cat name to equal “Wednesday”. The semantics of RSpec are powerful and make learning it easy.

Anatomy of a Test

Describe

  • Used to define an example group inside of a Ruby block
  • RSpec method that takes in one argument of a string or class name. This argument will be really helpful when reading the output when a test is run.
  • “context” is an alias for describe and can be used interchangeably. Semantically, it is used within the context of a given test.

It 🤡

  • Used to define an example inside of a Ruby block
  • RSpec method that takes an argument of a string that will be used mostly for output.

Expect

  • used to define expected outcomes

Expectations 👩‍🏫

Expectations in RSpec have a a basic structure and they are as follows:

There are really four main parts to expectations:

  • expect
  • the expect(argument)
  • .to or .not_to method
  • and then an argument to the .to/.not_to method. These are the “matchers”

Matchers

Matchers are abundant in RSpec. Click here to get a more in depth look at just how many there are! Here are some of the more common types:

Equality Matchers

Lets take a moment to consider Ruby’s comparison operators.

  • The ‘==’ operator in Ruby will compare two values on the basis that both are roughly equal. So values like 1 and 1.0 are going to return true when compared using ‘==’ even though the data types are different.
2.6.1 :001 > 1 == 1.0=> true
  • Ruby has different levels of strictness when using comparison operators. Ruby will make a type comparisons using the .eql? method and since integer != float, we have:
2.6.1 :001 > 1.eql?(1.0)=> false

Lastly, Ruby has an Object comparison method that is the strictest. If an object stored in memory is not the exact same, it will return false:

2.6.1 :001 > tom = "Human"
=> "Human"
2.6.1 :002 > josh = "Human"
=> "Human"
2.6.1 :003 > tom.equal?(josh)
=> false
2.6.1 :004 > tom.object_id
=> 70274033337080
2.6.1 :005 > josh.object_id
=> 70274033321180
2.6.1 :006 > tom.equal?(tom)
=> true

There are similar levels of strictness in RSpec when testing equality. Here is the Ruby version and it’s RSpec counterpart:

Collection Matchers

Strings 😺

string = “ReSPECt”

  • include(string) — expect(string).to include(“SP”) — you can also use Regex — expect(string).to include(/([A-Z])\w+/) — just make sure that your Regex skills are awesome if you use this because it could work against you.
  • start_with(string) — expect(string).to start_with(“Re”) — or expect(string).not_ to start_with(“SP”)
  • end_with(string) — expect(string).to end_with(“Ct”) — or expect(string).not_to end_with(“Re”)

Arrays 🗳

array = [1, 2, 3, 4]

  • include(int) this matcher works like the string include matcher. Keep in mind that order does not matter here. You can check to see if an array contains the elements and if they are in the array, it returns true. expect(array).to include(2, 4)
  • start_with(int) —expect(array).not_to start_with(2)
  • end_with(int) — expect(array).to end_with(4)
  • match_array(arr) — expect(array).to match_array([2, 1, 4, 3])
  • contain_exactly(arr) — expect(array).not_to contain_exactly([2, 3])

Hashes 🍰 🍎

hash = {:apple => 1.00, :cake =>10.00}

  • include(:key) — expect(hash).to include(:cake) — use this matcher when you’re expecting a certain key to show up in your hash. You can provide more than one key as well. You can even check for key/value pairs — expect(hash).to include(:cake => 10.00)

Boolean Matchers

Be 🐝

Let’s first talk about the syntactic sugariness in RSpec. We are able to write tests like:

  • be(true) — expect(2>1).to be true
  • be(false) — expect(1>2).to be false
  • be(nil) — expect(nil).to be nil — be_nil is the same thing in this case
  • be_truthy — expect(“string”).to be_truthy
  • be_falsey — expect(nil).to be_falsey

There are many more matchers that you have at your disposal and you can find more info here!

Conclusion 🔚

RSpec encourages you to think about your code in a different way and because of that, you can create better, concise code. This can also make writing Ruby more enjoyable as if it weren’t already intensely fun already! I hope this at least gets you started looking for more answers to one of my favorite frameworks!

--

--

Tom Tobar
The Startup

Motorcycle Mechanic turned Full Stack Web Dev