2019年3月26日火曜日

Ruby で mock なテストをする

概要

RSpec で mock を使うには rspec-mocks を使います
基本的な rspec-mocks の使い方を紹介します
また Sinatra アプリと Sidekiq のワーカーのテストを書く際に rspec-mocks を使ってテストする方法も紹介します

環境

  • macOS 10.14.3
  • Ruby 2.5.1p57
    • sinatra 2.0.5
    • sidekiq 5.2.5
    • rspec, rspec-mocks 3.8.0

環境準備

今回は以下のような環境で行います

  • Sinatra が動いている
  • Sinatra が受けたリクエストは Sidekiq に流す
  • Sidekiq のワーカーが外部のサイトにリクエストしてレスポンスを得る

テストを作成するケースとして今回は

  • Sinatra アプリのテストを mock を使って実装する
  • ワーカーのテストを mock を使って実装する

をやってみたいと思います
各種テスト用のコードは以下の通り

  • tree -I "vendor*|tmp*"
.
├── Gemfile
├── Gemfile.lock
├── app.rb
├── config.ru
├── lib
│   ├── my_http.rb
│   └── my_http.rbu
├── spec
│   ├── app_spec.rb
│   ├── spec_helper.rb
│   └── worker_spec.rb
└── worker.rb
  • vim Gemfile
gem "sinatra"
gem "rspec"
gem "rspec-mocks"
gem "rack-test"
gem "sidekiq"
  • cat app.rb
require 'sinatra/base'
require './worker.rb'

class Web < Sinatra::Base
  get '/' do
    Worker.perform_async
  end
end
  • cat config.ru
require './app.rb'
run Web
  • cat worker.rb
require 'sidekiq'
require './lib/my_http.rb'

class Worker
  include Sidekiq::Worker

  def perform
    res = MyHTTP.new.call
  end
end
  • cat lib/my_http.rb
require 'net/https'

class MyHTTP
  def call
    uri = URI.parse 'https://kaka-request-dumper.herokuapp.com/'
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    req = Net::HTTP::Get.new uri.request_uri
    res = http.request req
    res.code
  end
end

アプリは

  • bundle exec rackup config.ru

ワーカーは

  • redis-server
  • bundle exec sidekiq -r ./worker.rb -P ./tmp/sidekiq.pid

で起動できます
これらのテストを mock を使って実装してみます

rspec テストの初期化

まずは spec ファイルを作成しましょう

  • bundle exec rspec --init

そしてそれぞれのテストを作成します

  • touch spec/worker_spec.rb
  • touch spec/app_spec.rb

Sinatra アプリのテスト

まずは Web アプリのテストを作成してみましょう
mock するのは Sidekiq のワーカーになります

  • vim spec/app_spec.rbv
ENV['RACK_ENV'] = 'test'

require './app.rb'
require 'rspec'
require 'rack/test'

describe "Web Tests" do
  include Rack::Test::Methods

  def app
    Web
  end

  it "could be fetch the job id" do
    allow(Worker).to receive(:perform_async).and_return("test_jid")
    get '/'
    expect(last_response.body).to eq "test_jid"
  end
end
  • bundle exec rspec -f d spec/

でテストが実行されます
Worker.perform_async を mock (どちらかと言えば stub) しているため実際に redis へはデータの登録は行われません
なのでワーカーは起動してない状態でもテストが通ります

ポイント

allow(Worker).to receive(:perform_async).and_return("test_jid") これかなと思います
allow は指定のクラスを mock するためのメソッドです
receive に mock するメソッドを指定します
返り値も mock したい場合は続いて .and_return で返り値を指定すれば OK です

これを get '/' の前に設定するのもポイントです
get '/' は実際に Siantra アプリにリクエストするので mock を作成スル前にコールしてしまうと redis にデータが登録されてしまいます

Sidekiq ワーカーのテスト

今度はワーカーのテストを書いてみます
先程とは違い mock するのは my_http.rb になります

require './worker.rb'

describe "Worker Tests" do
  it "could be fetch the response code" do
    my_http = instance_double(MyHTTP, :call => 200)
    allow(MyHTTP).to receive(:new).and_return(my_http)
    w = Worker.new
    expect(w.perform).to eq 200
  end
end

Worker.perform はクラスメソッドですが MyHTTP.call はインスタンスメソッドになります
なので mock の作り方も少し異なります
まず instance_doubleMyHTTP.call の mock を作ります
レスポンスは整数の 200 を返却するようにします
そして allowMyHTTP インスタンスメソッドとして call が呼ばれることを定義します
これで Sidekiq の Worker.perform 内で呼んでいる MyHTTP を mock することができます
実際テストを実行してみるとわかりますが HTTP の通信をしていないことが確認できると思います

最後に

rspec-mocks の簡単な使い方と Sinatra, sidekiq でのテストの書き方を紹介しました
まだまだいろいろな機能があるので興味があれば公式のページを見ると良いと思います

参考サイト

0 件のコメント:

コメントを投稿