In the last episode we saw how to set up our app. In this episode, we already have all our models up and running. We will start testing our API.

Project

Testing our API + Docs

We need to write tests for our API. We also need to document it for other developers. With RSpec API Documentation, we can do both at the same time. It has a DSL that generates documentation while verifying that the API works as described, all at once. Let's take a look!

By default all the tests are inside the spec/acceptance folder. Here we can see an example:

require 'spec_helper'
require 'rspec_api_documentation/dsl'

resource "Orders" do
  get "/orders" do
    example "Listing orders" do
      do_request

      status.should == 200
    end
  end
end

In this example we describe the Orders resource. We're describing how you can GET /orders. The example method receives a string explaining what the request is for, and the method do_request performs the request that's described.

We will create a helper for our tests. That will be located in the spec folder. We will call this acceptance_helper and we will use it in acceptance tests.

# frozen_string_literal: true

require 'rails_helper'
require 'rspec_api_documentation'
require 'rspec_api_documentation/dsl'

RspecApiDocumentation.configure do |config|
  config.format = :json
  config.post_body_formatter = :json
  config.curl_host = 'http://localhost:3000'
  config.api_name = 'Formulae API'
end

In this file, we will set up our API name, its base URL, how our POST body should be formatted, and many other options described in the documentation.

Let's create a test for our Question and we will use the acceptance_helper we have just created.

We will use the routes we have. If you don't know the route to what you're documenting, you can run rake routes to find out.

 get '/api/v1/questions/' do
    example 'Listing all the questions' do
      do_request
      expect(status).to eq 200
    end
  end

For this test, we're starting very simple. We'll just check if the response code is 200. Our second test will be to check our show method.

  get '/api/v1/questions/:id' do
    example 'Getting a specific Question' do
      do_request

      response = JSON.parse(response_body)
      expect(response.keys).to eq %w[id key label content order hidden]
      expect(status).to eq 200
    end
  end

In this case, we:

  • make a request
  • read the response body
  • parse it as JSON
  • make sure it has the keys we expect
  • ensure we get a 200 OK HTTP status.

This is a useful test and rspec_api_documentation made it easy to document and test the request. We are checking the keys id, key, label, content, order, and hidden. Let's run our tests.

Our test has failed because we are also returning created_at and updated_at. Let's add these fields in our test.

After adding them, our tests pass.

Let's do a test to show we can update a question. We'll update the key:

  put '/api/v1/questions/:id' do
    parameter :key, scope: :question
    let(:new_value) { 'updated' }
    let(:key) { new_value }

    example_request 'Updating a question' do
      response = JSON.parse(response_body)
      expect(response['label']). to eq q.label
      expect(response['key']). to eq new_value
      expect(status).to eq(200)
    end
  end

We are describing a parameter for the API call, and using rspec's let to pass the parameter. We're scoping the parameter inside a question key. This describes the JSON we're POSTing. The id is taken from another let that specifies the id of a Question we created in preparation for the test, earlier in the file.

In our test, we pass a new value for our key and we verify that the JSON is returning this new value. Our test passes!

Let's do a test to check our delete method. If a question is deleted, we return 204, so, in our tests we will check exactly this.

  delete '/api/v1/questions/:id' do
    example_request 'Deleting a question' do
      expect(status).to eq(204)
    end
  end

The methods of this DSL - like example_request, parameter, and others - are used when generate the documentation. This way, while we are writing tests for our API we are also creating our documentation.

Our tests pass!

To generate our documentation we just need to run a rake task that updates it for us:

rake docs:generate

And then we can open the generated docs:

open doc/api/index.html

There are various viewers we can use - one is APItome - to improve how our docs look.

Serializers

We are already returning some JSON from our API, but we would like to have more control over how it will look. To achieve this, we will use a library that handles serializers for us: Active Model Serializers. It helps us more easily describe our JSON response shape and manage how our JSON looks, even when we have associations between our models.

Let's add active_model_serializers in our Gemfile and run a bundle. Now we will create a serializer for our Question. In our serializer we will add only the fields we want to show in our JSON.

This is how you can have control over what your JSON will look like. You can create your own serializer or decorator, of course, without this library - but it's a fair bit of work. Here active_model_serializers helps us a lot!

In our questions controller, we are returning created_at and updated_at, but we don't want to return them. In this case, we will just not add these in our serializer.

We will create our Question serializer in /app/serializers/, and we will create our serializer without id, created_at and updated_at.

frozen_string_literal: true

class QuestionSerializer < ActiveModel::Serializer
  attributes :key, :label, :content, :order, :hidden
end

Let's change our test. It passes!

If we want to change the fields, we just need to change the QuestionSerializer and it will use the attributes we want. This is pretty handy: in all the places the question is returned, it will call the serializer.

For example, if we add or remove the id for our Question, and we refresh the result, we can see it! The same thing happens if we remove the key.

Summary

In this episode we saw how to test our API, and used those tests to generate documentation for our API, using rspec_api_documentation. We also saw how to create serializers for our API with active_model_serializers to help us control how our JSON responses look. I hope you enjoyed it. See you soon!

Resources