RでAPIを叩くパッケージのテストがしたいときはdevtools::with_mock()とsaveRDS()

誰かに教えてもらったまま理解できず放置してたけど、やり方分かったのでメモ。

こんな感じの、APIを叩く関数があったとします。

api_tataku <- function(apikey, param1, ...) {
   res <- httr::GET("http://example.com", query = list(apikey = apikey, param1 = param1, ...))
   httr::content(res)
}

テストのたびにAPIを叩きたくはない

これをテストするときは、たぶんこんな感じのテストを書くことになります。

expect_identical(api_tataku("XXX", "abc"))

でもテストが走るたびにAPIが叩かれるのは困ります。利用回数に制限があるAPIだと特に。

あと、APIキーをがあるようなやつだと一般公開するわけにはいかず、そうなるとテストを公開のレポジトリに入れられなくなるので困ります。

with_mock()

こんなときは、with_mockでモックを使います。具体的には、httr::GETを、外部にアクセスせず固定の結果を返す関数に変えてしまいます。

with_mock(
  `httr::GET` = function(...) arakajime_kimerareta_result,
  expect_identical(api_tataku("XXX", "abc"))
)

saveRDS()

で、その固定の結果はどうやって用意しておくかと言うと、実際のAPIを叩いた結果をsaveRDS()で保存しておきます。

ただし、httr::GET()が返す情報にはリクエストとかクッキーとかまずいものが入っているので、contentだけを保存しておきます。(注:これも、contentにはまずい情報がはいってない場合だけに許されることです。細心の注意を払いましょう!)

res <- httr::GET("http://example.com", query = list(apikey = apikey, param1 = param1, ...))
saveRDS(res$content, file = "tests/testthat/api_tataita_result.rds")

これをreadRDS()で読み込んで、structure()を使ってそれっぽいresponseオブジェクトをつくれば完成です。それっぽいかとはどのくらいなのかは場合によりますが、Content-Typestatus_codeくらいは入れておいた方がよさそうでした。

arakajime_kimerareta_result <- structure(list(
  content = readRDS('api_tataita_result.rds'),  # tests/testthat/ からの相対パスになる
  url = '',
  headers = list(`Content-Type` = "application/json;charset=utf-8"),
  status_code = 200
),
class = "response")

最終的にはこんな感じになりました。

github.com

ちなみに、この程度のことで私はひーこら言ってましたが、ゴミ箱さんは当たり前のように使っててやっぱすごいなあと思いました。

github.com