Edited at

簡単なAPIを作成し、テストコードも書いてみる

More than 3 years have passed since last update.

Ruby on Railsを使用して、テストコードを含めて簡単なAPIを作成してみようと思います。

私自身が初心者ですので、内容は非常に初心者向けになります。

基本はパーフェクト Ruby on Railsを見ながら実施し、補足などあれば作業メモとして残していきます。


今回作成するAPIについて

クライアントからリクエストされたIDでDBの値を返します。

そのため、適当なテーブルを作成します。

簡単なscaffoldを利用します。

$ rails g scaffold item name:string price:integer description:text

$ rails db:migrate

こんなテーブルが作成できました。

id
name
price
description
created_at
updated_at

integer
varchar
integer
text
datetime
datetime

NOT NULL, PRIMARY KEY

NOT NULL
NOT NULL


余談


SQLiteでSHOW COLUMNSをやる

(PHP, MySQL, SQLite3) テーブルのフィールド名一覧を取得するには

PRAGMA table_info(items);


rails g scaffoldで特定のファイルを生成しない

今回は全て生成してしまいましたが、今後assetsなどを生成したくないときは下記の設定を追記する必要があるようです。

Railsのgenerateコマンドを俺好みに設定する


config/application.rb

+ config.generators do |g|

+ g.helper false
+ g.assets false
+ g.test_framework false
+ g.orm :active_record, migration: false


itemsテーブルのレコードを返すAPI

まず、コントローラーとビューを作成しました。

(erbファイルについては割愛します。)


app/controllers/api_controller.rb

class ApiController < ApplicationController

# GET /get_items
# GET /get_items.json
def get_items
@items = Item.all
respond_to do |format|
format.html
format.json { render json: @items }
end
end
end


routes.rb

+ get 'api/get_items', to: 'api#get_items'


これでapi/get_items.jsonでアクセスすると、JSON形式で返してきます。

スクリーンショット 2016-10-02 18.07.53.png

これはこれで良いかな、とも思いましたが、Ruby on RailsにはJbuilderというJSON生成のGemがあります。

こちらは初めからGemfileに記述されています。


Gemfile

# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder

gem 'jbuilder', '~> 2.5'

実はscaffoldで生成すると勝手に生成してくれてもいました。

jbuilderファイルを作成する意味としては、複数のモデルの値を返却したり、URLなどを返却するなど複雑な返却値のとき使用するようです。

今回は、scaffoldで生成されていたファイルを参考に、以下のように作成しました。


get_items.json.jbuilder

json.array! @items do |item|

json.extract! item, :id, :name, :price, :description, :created_at, :updated_at
json.url item_url(item, format: :json)
end

これで、itemsテーブルの各カラムの値とURLが返されるようになりました。

(URLはお好みかと思います。)

このjbuilderファイルを作成しておくと、コントローラーの方でもjbuilderファイルを参照するため、下記のように変更できます。


app/controllers/api_controller.rb

- format.json { render json: @items }

+ format.json

これでも下記のようにブラウザで確認できると思います。

スクリーンショット 2016-10-02 18.05.43.png


APIリクエスト時にパラメーターを渡される想定

例えば、items.idの2〜5まで欲しいなどのリクエストを想定し、下記のように修正します。


app/controllers/api_controller.rb

- @items = Item.all

+ before_id = params[:before_id]
+ after_id = params[:after_id]
+ @items = Item.where(id: before_id..after_id)

これで@itemsに指定された範囲のレコードが入るので、JSON生成も絞られます。

ブラウザで確認もできますが、テストコードも書いてみたいと思います。


Rspecのインストール

Ruby on Railsには多くのテストフレームワークがあるようですが、今回はRspecを利用します。

Rspec → http://rspec.info/about/


Gemfile

group :development, :test do

# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platform: :mri
+ gem 'rspec-rails'
end

インストール後、Rspecの設定ファイルを生成します。

$ bundle install

$ rails g rspec:install
create .rspec
create spec
create spec/spec_helper.rb

とりあえず設定ファイルは置いておき、モデル用のテストファイルを生成します。

$ rails g rspec:model item

create spec/models/item_spec.rb

簡単なテストコードを書いてみます。

Rspecの書き方については、こちらの方が非常に分かりやすく記事にしてくださっております。

使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」


spec/models/event_spec.rb

require 'rails_helper'

RSpec.describe Item, type: :model do
describe '#name' do
context '空白のとき' do
let(:item) { Item.new(name: '') }

it 'validでないこと' do
item.valid?
expect(item.errors[:name]).to be_present
end
end
end
end


テストしてグリーンになればOKです。

$ bundle exec rspec spec/models/item_spec.rb


余談


Rspecをインストールした後にscaffoldを生成すると、specディレクトリ以下が生成される

Rails Q&A「Scaffoldで作成されるテストはそのまま使うべきか?」


FactoryGirl

テスト用にレコードを作成してくれるFactoryGirlを使ってみます。

FactoryGirlのGithub → https://github.com/thoughtbot/factory_girl


Gemfile

group :development, :test do

# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platform: :mri
gem 'rspec-rails'
+ gem 'factory_girl_rails'
end

FactoryGirl用のファイルも生成します。

$ bundle install

$ rails g factory_girl:model item
create spec/factories/item.rb

FactoryGirlの定義をします。


spec/factories/items.rb

FactoryGirl.define do

factory :item do
sequence(:name) { |i| "商品名#{i}" }
sequence(:price) { |i| i }
sequence(:description) { |i| "説明#{i}" }
end
end

これで、FactoryGirl.create(:item)でレコードをインサートしてくれます。

試しにコンソールで確認します。

$ rails c

Running via Spring preloader in process 50595
Loading development environment (Rails 5.0.0.1)
irb(main):001:0> FactoryGirl.create(:item)
(0.1ms) begin transaction
SQL (2.9ms) INSERT INTO "items" ("name", "price", "description", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["name", "商品名1"], ["price", 0], ["description", "説明1"], ["created_a016-10-02 05:32:52 UTC], ["updated_at", 2016-10-02 05:32:52 UTC]]
(3.6ms) commit transaction
=> #<Item id: 1, name: "商品名1", price: 0, description: "説明1", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52">

2回目は「商品名2」、3回目は「商品名3」。。。と入っていきます。

こちらをテストコード中で呼べば、オブジェクトを生成してくれますが、FactoryGirlの設定ファイルに下記のように記述すれば、create(:item)のみで呼べるようになります。


spec/spec/helper.rb

+ require 'factory_girl_rails'


RSpec.configure do |config|

+ config.include FactoryGirl::Syntax::Methods

# 以下、省略


このFactoryGirlを利用して先ほどのテストコードに追記してみます。

今回はちゃんとしたレコードが入るので、「名前が空欄でないときはエラーが出ない」ことを確認します。


spec/models/item_rspec.rb

+    context '空白じゃないとき' do

+ let(:item) { create(:item) }
+
+ it 'validであること' do
+ item.valid?
+ expect(item.errors[:name]).to_not be_present
+ end
+ end

こちらでもオールグリーンになりました。

bundle exec rspec spec/models/item_spec.rb


API用のテストコードを書いてみる

下記のような形で、GETでパラメーターを渡し、取得したレコード数、before_idで取得した値、after_idで取得した値を比較しております。

今回はAPI用のテストコードになるため、spec/requestsディレクトリに作成します。

FactoryGirlcreate_listメソッドですが、これは第2引数で渡した数だけレコードを生成してくれます。


spec/requests/items_rspec.rb

require 'rails_helper'

RSpec.describe "Items", type: :request do
describe "GET /api/get_items" do
let(:path) { '/api/get_items.json' }
context "GETパラメータ-が渡されているとき" do
before do
@items = FactoryGirl.create_list(:item, 10)
@params = { before_id: 2, after_id: 5 }
end

it "指定範囲のレコードが取得できる" do
get path, params: @params

expect(response.status).to eq 200

json = JSON.parse(response.body)
expect(json[0]['id']).to eq @items[1].id
expect(json[3]['id']).to eq @items[4].id

expect(json.size).to eq(4)

end
end
end
end



余談


デバッグするとき

いろいろ方法はあるかと思いますが、とりあえずテストコード中にputsで埋め込んでおりました。

(PHPのときもvar_dumpやログ出力の簡単なものしか使っていなかったので。。。)


spec/requests/items_rspec.rb

      it "指定範囲のレコードが取得できる" do

get path, params: @params

expect(response.status).to eq 200
+ puts response.body
json = JSON.parse(response.body)
expect(json[0]['id']).to eq @items[1].id
expect(json[3]['id']).to eq @items[4].id

expect(json.size).to eq(4)

end



最後に

私がRuby on Railsの勉強を開始するときに参考にさせていただいたRUBY ON RAILS TUTORIALでも著者が書いておりましたが、「何かの機能を追加するとき、まずはテストコードから書く」という習慣を身に付けたいと思います。

簡単ではございますが、今回はこれで終了とさせていただきます。