Piotr Jaworski
Ruby on Rails

Rails API with Active Model Serializers – Part 2

   Back to list

In the previous installment of this series, I covered how to build a solid API using Rails 5. Today, I want to show how we should test our API and how to write clean and readable tests using RSpec.

Tests are a really important part of our application. Without them, it’s hard to say which part of the application works, refactor some code or even add a new feature without breaking existing functions. Moreover, good specs are like a system documentation; they show how each part of the application or method should behave.

RSpec setup

We’ll use RSpec as a test framework instead of MiniTest. Please add the RSpec, FactoryGirl and ShouldaMatchers gems to the Gemfile:

  • RSpec is a test framework for Ruby and Rails applications. It’s really easy to write specs using it. Moreover, if you want test the front-end part of your application, just add the Capybara gem – it painlessly integrates with RSpec!
  • FactoryGirl is used to create factories for our tests, basically to build some records.
  • ShouldMatchers helps to test associations, validations and some controller methods.

Now when you know what each part of a system does, let’s install RSpec and initialize it:

And run it to check if everything works ok!

Gems setup

In this case adding gems to the Gemfile isn’t enough. We need to add a small configuration to the application. Please add to this line rails_helper.rb, which enables FactoryGirl:

In order to configure ShouldaMatchers, add to rails_helper.rb:

Also we need to enable pundit test methods. Add pundit to rails_helper.rb (top of the file):

Add factories

Now that we have written some specs, let’s prepare all needed the factories. We need admin, user, author, book and book_copy factories. Create a folder called factories under specs and add the first factory – admin.

As you can see it’s really simple. We just define all needed attributes with their values. As you can see, to define an email, I used sequence. What is it? In each created factory, it fills a record with a sequence value. Why do we need it? Because an email address must be unique – and it is unique, thanks to sequence.

Now please add the rest of the needed factories:

Model specs

Everything is ready to write the first spec – let’s do it. We need to test all the associations, validations and methods in the Author class. Create an author_spec.rb file under the specs/models:

What are we testing here? We will check if all validations and associations are present. If you remove one, tests will fail (comment out associations):

Now let’s test the BookCopy class. Testing associations and validations isn’t enough. We have 2 more methods there – borrow and return book. All of these methods should be tested in two ways – successful and unsuccessful scenarios. Let’s write the BookCopySpec:

As you maybe noticed, I like to use one-liners. They’re really clear and readable to me. Moreover, I have a rule that I first declare variables using let. I call the before/after block later, and at the bottom I declare a subject. It really helps me to maintain, organize and read my code.

BookSpec looks really similar to BookCopySpec but there, we need to test the static-class method:

Something new is in the UserSpec. Here we need to test the before_save callback. How? Well, we should check to see if a method has been called – using expect(instance).to receive(:method_name).

Another thing to test is if a method does what it should do. I check what happens before and after saving an instance:

Controller’s specs

Most of the important specs are controller’s specs. We must test if our endpoints work properly. Also, if we want to modify any method or refactor it, without specs it’s really hard to do.

Specs also have another responsibility, they show how each endpoint should behave. Moreover, in a case when each endpoint does something different for each user role, we should test all the possible cases. We don’t want to give an access to sensitive data for not-permitted users. Let’s start with writing specs for AuthorsController.

Let’s start at the index method. What should we test for here? If it’s accessible only for admins and to see if it returns a valid JSON with records. To pass HTTP Token, you can add to a before block:

Now let’s test the show method. It should also not be accessible for users – only for admins. Moreover, a JSON should return attributes which are specified in a serializer.

Create method – also accessible only for admins. Moreover, we need to test two scenarios – with valid params and invalid – to check if our validations work.

Update method – the flow is really similar to the create method.

Destroy method – records can be removed only if the user is an admin.

As you probably noticed, I use one-liners to test each endpoint’s HTTP status, for example:

But not all HTTP statuses have methods which allow us to write tests like this – for example 422. There is not something like it (and if there is, I haven’t seen it :P):

In this case, you need to directly pass a HTTP status name:

Here is a full file with AuthorController’s specs, so if you get lost somewhere, feel free to check it:

BookCopiesController, BooksController and UsersController look almost the same as the AuthorsController so the specs are also almost the same. I won’t cover them, but below you can find the full files.

BookCopiesControllerSpec:

BooksControllerSpec:

UsersControllerSpec:

Now, I want to focus on two methods which are accessible to users and admins – borrow and return_book in the BookCopiesController.

There are a lot of cases; first let’s analyze the borrow method. A book can be borrowed by an admin, when he passes a user_id parameter. Without it, he can’t borrow. Moreover, a book copy cannot be borrowed if it’s already borrowed. As a user, we can borrow a book if it’s not borrowed – really simple.

Now the return_book method. An admin can return a book only with a user_id parameter. Moreover, user_id doesn’t need to match to a book copy’s user_id – an admin is an admin 🙂 and a not borrowed book cannot be returned.

A user can only return a borrowed book by himself, he cannot return a book that doesn’t belong to him.

PolicySpecs

We should also test our policies. They’re a really important part of the application. Right now, in the application we only have one policy. Let’s write some specs – please add the book_copy_policy_spec.rb under the specs/policies folder.

As you can see, we tested the return_book? method for an admin and a user. Each case should be covered. Pundit provides some useful methods for RSpec to make testing really simple.

Test coverage

One of the important things when we talk about testing is test coverage. It shows the amount of lines of code covered by tests. Also it shows which file has the least test coverage and which lines should be covered. SimpleCov is a great gem which can build a detailed report about our tests and show a test coverage rate.

Please add to the Gemfile (test group) and install:

To make it work, add at the top of the rails_helper.rb:

Now, when you run your tests, SimpleCov will prepare a report.

As you can see, our tests cover the code by 96.5% – which is a great result! Moreover, when you open the coverage/index.html file, it shows a detailed report, which covers how each file has been covered by specs.

Conclusion

In this part of the series, I covered how to write readable and clear specs for your API. You should keep in mind that testing is a really important part of your application. Every good application should include specs which say how each part of the application should behave.

I hope that you liked this series and find it useful! The source code can be found here.

If you want to stay up to date with our articles, please subscribe to our newsletter. Feel free to add your comments and thoughts below!

  • urikanegun

    Good article. I’d like to translate and publish the part 1-2 on our tech blog https://techracho.bpsinc.jp/ if you’re OK.
    I make sure to show the URLs to yours, author name etc.

    Best regards,