RSpec is an incredibly powerful Ruby/Rails meta-gem that allows your to write very simple tests that test the efficacy of very complex code. It’s a meta-gem, because when it is bundled it includes multiple different versions of the same basic gem. Each version can be used for specific purposes or with specific versions of Rails. When I reference RSpec, I will be referring to the meta-gem, not a specific version.
Test-driven-development is a very important skill to possess as a software engineer, but knowing where to start can sometimes seem a daunting task. I spent weeks in a software engineering bootcamp writing methods and models in labs that were thrown at me day after day. I would run the tests that my instructors gave me to run to see if my code worked. We chased the green response (you will soon know what I mean) like our lives depended on it. I quickly realized that if I learned to read the tests that I could write the deliverables at twice the speed. It only recently occurred to me that had I learned to write the tests at that time also, that I may have been able to increase my performance to another degree. And for those who haven’t been in a software engineering bootcamp, let me tell you, speed kills.
My intention with this guide is simple; I want to scratch the surface. That is all. The difference between knowing nothing and knowing a little is immense in this instance. Knowing how to read and write the most basic of tests gives you the ability to learn more advanced methods very quickly. So, please code along with me if you like, and by the time you reach the end of the page you will have written your first fully-functioning RSpec test.
Getting Ready to Test
The first step is to create a new app using the Rails generator. There are multiple flags that you may want to include when generating a new app to modify the files that Rails generates. You can easily bring up the help menu that will list all these commands to you by typing ‘rails --help’ in the terminal.
Navigate into your newly formed app directory, and open the files in your text editor. In this example, I am using VSCode. Check to make sure you do not have a ‘test’ directory, and be sure to delete it if you do. Next, we need to add the RSpec gem to the Gemfile as you would any other gem.
Once this is complete, return to your terminal and do a standard ‘bundle install’ to make sure your RSpec gem is accessible. Now it is time to initialize RSpec in your app. Still in terminal, type ‘rspec - -init’. This will build out the necessary RSpec directory and files so you can begin writing your tests.
The ‘/spec’ directory is where you will build out all of your test files, and the ‘.rspec’ file will contain any settings changes you wish to apply to the gem. The spec_helper.rb file is basically the gateway for all support code when using RSpec. There are plenty of modifications we can make here, but for now we will keep it as simple as possible. Since our tests live in a different directory than the model we will be testing, we need to include a ‘require’ with the path to the model.
Writing Our Test
Fantastic! We have our new app framework built, we have our gems installed, we have our RSpec suite initialized, and we have the path that we will need to be testing across listed. Time to write our first test. To do this, we need to create a new file to write our test in. This new file will be in our /spec directory, and it’s name will be the same as what we will be naming our model, followed by ‘_spec’. Since we plan on naming the model ‘stats_calculator.rb’, my spec file will be named ‘stats_calculator_spec.rb’.
So, what are we testing? The deliverable that I just made up for use to use is a method that can take in an array of integers as its argument, and return the average value, or mean, of the array. Simple enough. Below is the beginning of the test that will tell us if our method yields the results we are looking for.
Just like with the rest of Ruby, the tests read rather clearly. We begin by building our tests under ‘RSpec.describe <name-of-the-model> do’. All of the tests for our ‘StatsCalculator’ model will exist here. The next sub-level of our testing is written at the method level. We begin by writing “describe ‘<#method-we-are-testing> do”. Each test that exists in this ‘do’ block will be run against the #average method. If our model had other methods in it this is how we would differentiate between them when testing. Finally, we write the test at a granular level. This is called the it-block. We start with ‘it’ and we follow that with a string of real language that will describe what the test is doing. In this case, the test ‘returns the average of the arguments’. It doesn’t matter what we put in this string, but you will see later that it is meant to be specific and communicate with the person coding.
Line 5 is the meat of the test. It almost reads like English. What this test is saying is: “we expect that when we build an instance of StatsCalculator and pass an array of values through #average method that the method will return a value equal to ‘6’. Since the instance variable ‘@values’ doesn’t yet mean anything in the scope of this test, let’s give our test some real numbers to work with.
Lines 3–5 set an instance variable ‘@values’ equal to an array of integers. These integers are chosen specifically because we know that the average of this set is equal to 6. Each it-block of code is considered an example. So, we run ‘before(:example) do’ to make sure ‘@values’ is set and isn’t nil while the test is being run.
Running the Test & Development of Our Model
Our environment is set. Our test is written. Let’s run it! To run the test, just type ‘rspec’ in terminal. If you have followed along correctly, the following is what you should see:
Our first error! These are going to become your friend, because they will guide you to the solution with very specific details. This is one of the amazing aspects of Ruby. Our error is a LoadError. It is saying that it couldn’t load ‘stats_calculator’. This should seem obvious though, because we haven’t created it yet! Let’s quickly build the model in the directory we chose earlier and run the test again.
Our error changed. That is almost always a good thing. We have located the StatsCalculator, but we get a new error; NoMethodError. The method #average is undefined in our model. Easy fix. Let’s define the method and run the test again.
Again, the error changed. #average is found, but according to the test it should accept an argument. The test is giving us 1 argument, but the method expected 0 arguments.
Ok. I understand this is a bit tedious. Who in their right mind would write a simple averaging method this way? Most likely no one. BUT, this is just meant to show you how different errors can lead you to solutions. When you are writing much more complicated methods having step-by-step error-driven instructions can be incredibly helpful. Crawl before we walk.
Anyways, now we are getting really close. The test ran to completion and did a comparison using ‘==’ (our .to eq(6) is what did this). It expected the answer to be 6 after feeding the seed array we created in our test through the method. Well, of course it’s nil. There’s no code block in the method! Let’s give it some and run it one more time.
GREEN! We got green folks! We also got the little green dot we referenced earlier. The feeling of red-to-green is very exciting when you have been working on a method for a long time and finally get it working. This now proves that our #average method works properly. Let’s make this just a little bit easier to read for ourselves though. Add this line of code to your ‘.rspec’ file:
Format documentation will cause your tests to read more like English in the terminal. The same test now looks like this when run:
This isn’t a big deal with just our one little test. However, when you run multiple tests at the same time it is important to be able to see exactly what is happening in a clear-cut manner. Here is a better example from a different set of tests run on another app:
As you can see, things can get complicated quickly. Having the tests tell you what is happening in such a verbose way makes your life so much easier when fixing errors or seeing what is and isn’t working.
Congratulations! You have now written and tested your first RSpec test. This tutorial barely scratched the surface as I stated at the beginning. There is so much that can be done with RSpec. My only hope is that this puts you in a position to be literate enough in RSpec to move on to the next level and begin writing more advanced tests of your own. Good luck!