Build a TDD RESTful JSON TODO-List API (pt.2)

Fixing the failures

 

…by editing our two models:

/app/models/todo.rb

# app/models/todo.rb
class Todo < ApplicationRecord
  # model association
  has_many :items, dependent: :destroy

  # validations
  validates_presence_of :title, :created_by
end


app/models/item.rb

class Item < ApplicationRecord
 belongs_to :todo

# validation
 validates_presence_of :name
end

running bundle exec rspec again will result in 0 errors as expected:

Finished in 0.87063 seconds (files took 4.09 seconds to load)
5 examples, 0 failures

 

 

Controllers

 

rails g controller Todos

rails g controller Items

 

We’re not testing like we did with our models, instead we use Request Specs.

Request Specs are specifically designed to test the full stack of an app. Means even HTTP endpoints.

 

Creating a own folder for request in spec directory:

mkdir spec\requests && touch spec\requests\{todos_spec.rb,items_spec.rb}

 

Defining the factories

by wrapping faker inside the block of code, we are guaranteed to get unique titles and created_by’s every time the factory is invoked:

FactoryGirl.define do
  factory :todo do
    title { Faker::Lorem.word }
    created_by { Faker::Number.number(10) }
  end
end 

FactoryGirl.define do
  factory :item do
    name { Faker::StarWars.character }
    done false
    todo_id nil
  end
end

 

and the Todo API:

 

require 'rails_helper'

RSpec.describe 'Todos API', type: :request do
  # initialize test data 
  let!(:todos) { create_list(:todo, 10) }
  let(:todo_id) { todos.first.id }

  # Test suite for GET /todos
  describe 'GET /todos' do
    # make HTTP get request before each example
    before { get '/todos' }

    it 'returns todos' do
      # Note `json` is a custom helper to parse JSON responses
      expect(json).not_to be_empty
      expect(json.size).to eq(10)
    end

    it 'returns status code 200' do
      expect(response).to have_http_status(200)
    end
  end

  # Test suite for GET /todos/:id
  describe 'GET /todos/:id' do
    before { get "/todos/#{todo_id}" }

    context 'when the record exists' do
      it 'returns the todo' do
        expect(json).not_to be_empty
        expect(json['id']).to eq(todo_id)
      end

      it 'returns status code 200' do
        expect(response).to have_http_status(200)
      end
    end

    context 'when the record does not exist' do
      let(:todo_id) { 100 }

      it 'returns status code 404' do
        expect(response).to have_http_status(404)
      end

      it 'returns a not found message' do
        expect(response.body).to match(/Couldn't find Todo/)
      end
    end
  end

  # Test suite for POST /todos
  describe 'POST /todos' do
    # valid payload
    let(:valid_attributes) { { title: 'Learn Elm', created_by: '1' } }

    context 'when the request is valid' do
      before { post '/todos', params: valid_attributes }

      it 'creates a todo' do
        expect(json['title']).to eq('Learn Elm')
      end

      it 'returns status code 201' do
        expect(response).to have_http_status(201)
      end
    end

    context 'when the request is invalid' do
      before { post '/todos', params: { title: 'Foobar' } }

      it 'returns status code 422' do
        expect(response).to have_http_status(422)
      end

      it 'returns a validation failure message' do
        expect(response.body)
          .to match(/Validation failed: Created by can't be blank/)
      end
    end
  end

  # Test suite for PUT /todos/:id
  describe 'PUT /todos/:id' do
    let(:valid_attributes) { { title: 'Shopping' } }

    context 'when the record exists' do
      before { put "/todos/#{todo_id}", params: valid_attributes }

      it 'updates the record' do
        expect(response.body).to be_empty
      end

      it 'returns status code 204' do
        expect(response).to have_http_status(204)
      end
    end
  end

  # Test suite for DELETE /todos/:id
  describe 'DELETE /todos/:id' do
    before { delete "/todos/#{todo_id}" }

    it 'returns status code 204' do
      expect(response).to have_http_status(204)
    end
  end
end



Next we are going to define a JSON helper method that parses JSON response to a ruby hash to make it easier to work with in our tests:

 

module RequestSpecHelper
  # Parse JSON response to ruby hash
  def json
    JSON.parse(response.body)
  end
end

 

running our tests again will result into several routing errors:

 

Finished in 1.91 seconds (files took 4.07 seconds to load)
18 examples, 13 failures

Failed examples:

rspec ./spec/requests/todos_spec.rb:14 # Todos API GET /todos returns todos
rspec ./spec/requests/todos_spec.rb:20 # Todos API GET /todos returns status code 200
rspec ./spec/requests/todos_spec.rb:30 # Todos API GET /todos/:id when the record exists returns the todo
rspec ./spec/requests/todos_spec.rb:35 # Todos API GET /todos/:id when the record exists returns status code 200
rspec ./spec/requests/todos_spec.rb:43 # Todos API GET /todos/:id when the record does not exist returns status code 404
rspec ./spec/requests/todos_spec.rb:47 # Todos API GET /todos/:id when the record does not exist returns a not found message
rspec ./spec/requests/todos_spec.rb:61 # Todos API POST /todos when the request is valid creates a todo
rspec ./spec/requests/todos_spec.rb:65 # Todos API POST /todos when the request is valid returns status code 201
rspec ./spec/requests/todos_spec.rb:73 # Todos API POST /todos when the request is invalid returns status code 422
rspec ./spec/requests/todos_spec.rb:77 # Todos API POST /todos when the request is invalid returns a validation failure message
rspec ./spec/requests/todos_spec.rb:91 # Todos API PUT /todos/:id when the record exists updates the record
rspec ./spec/requests/todos_spec.rb:95 # Todos API PUT /todos/:id when the record exists returns status code 204
rspec ./spec/requests/todos_spec.rb:105 # Todos API DELETE /todos/:id returns status code 204

 

So we have to define our routes now.

 

config/routes.rb:

Rails.application.routes.draw do
  resources :todos do
    resources :items
  end
end

the items resource is nested inside of todos to get the one to many association.

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s