Testing API Endpoints with RSpec on Rails
In my recent article, I went over the differences between two very popular types of testing- Mocha and Jest. This time around I wanted to touch on RSpec, a testing suite for Ruby. Ruby on Rails is also my preferred backend, so I was more than happy to take on the task of learning a bit about RSpec for a recent interview.
My challenge was to create an API with custom endpoints and then build RSpec tests for each one of those endpoints. Lucky for me, my recent dive into the world of testing certainly helped, as the flow of logic is similar to what I had experienced while experimenting with Mocha and Jest. Let’s check out how I tested my endpoints using RSpec via the ‘rspec-rails’ gem.
Step 1: Start like a seed
When I first began my tests, I wanted my test data that was being created to be destroyed in the beginning every time I ran RSpec, just so I didn’t run into any weird database errors. It reminded me a lot of destroying my seed files before running rails db:seed.
So on Line 3 I’m initializing the RSpec test itself for all of my Device requests. After that, I create a ‘before do’ in my tests that will trigger first. That lets me take all of the information that is stored in my devices and start with a clean slate every time we run our tests.
On line 8, because we’re going to need to create some Device instances, I’m creating an instance variable for the attributes of a device. We’ll use this later.
Step 2: Testing a ‘POST’ Method to register a device
Now we’re getting started writing our tests. I’m going to go over two different types of ‘POST’ requests as well as a ‘PATCH’ request. Don’t get confused that these endpoints don’t fully follow CRUD, we just used some custom routes.
On line 30, we’re describing what our test should do. What should it do? We answer that on line 32 with our it statement. Now we know that this endpoint should do let’s make it happen!
On line 34, we’re sending our post request with the params necessary to create a device. We can’t use our instance variable yet because we don’t have a device in our database!
The next few lines are our assertions. We’re expecting our backend to accept that we’re posting a new Device to the database with the attributes we supplied it and return it in JSON format. In addition, we want to know for sure that this request worked by checking if our status code is ‘201’ or ‘created’.
Step 3: Testing a ‘PATCH’ Method to update a device attribute
Line 43 describes our test, and this time it’s a patch request. In order for our patch request to work, we need to already have a device in our database. That’s why before we do anything else we’re going to create a device in a ‘before do’ statement on line 45. Here is where we’ll use our ‘@device_attributes’ instance variable.
Now we’ll say what our test should do. We want it to update our disabled_at attribute to the current DateTime which is currently set to nil by default. We do that after our it statement on line 51.
Line 53 is our patch request, and we’ll send it along with the device_id because that’s how our backend would like it.
Finally we have our assertions. Here we check to see if our disabled_at attribute is no longer nil and that our status code is ‘202’ or ‘accepted’.
Step 4: Testing a ‘POST’ Method to create instances of other models.
In this API, we’re using a single controller to access all of our models. This is great for smaller applications, and means that we can keep everything in a single RSpec file while testing them, too!
Because our Models show that a Device has_many :heartbeats, we’ll need to create a Device before we can create a Heartbeat. Lines 62–64 here follow a similar flow of logic to our previous test’s beginning.
Line 66 describes our test, saying that it should create a new instance of a Heartbeat for our device. Line 68 does our ‘POST’ request, and like before, we send it with necessary params.
Our assertions on line 70 are a little different than before. That’s because instead of testing against a device attribute or a JSON.response, we just need to see if our heartbeat was successfully created. We do this by asserting that the size of this device’s heartbeats is equal to 1. Remember that we destroy our test database every time we run RSpec, so it should never be more than 1.
I think I did a pretty good job for my first time really diving into RSpec. I definitely recommend also testing your api with something like Postman so you can be extra sure that your endpoints work! Looking forward to getting better and learning more! Next step, error handling in my tests!