search
LoginSignup
5

More than 5 years have passed since last update.

posted at

updated at

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

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でも著者が書いておりましたが、「何かの機能を追加するとき、まずはテストコードから書く」という習慣を身に付けたいと思います。
簡単ではございますが、今回はこれで終了とさせていただきます。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5