TL;DR
espec_request_describerを作った。マクロや関数の動的呼び出しの知見が深まった。
rspec-request_describer
rspec-request_describerをご存知の方はそのパクり移植だと思っていただければ分かりやすいと思います。
知らない方のためにちょっと説明すると、rspec-railsでfeature specなどでリクエストをするテストの時に、describe "GET /users"
のようにHTTP_METHOD PATH
の形式でdescribeを書くと、それに沿ったリクエストを構築してくれるgemです。
例えば
describe "GET /users" do
it { is_expected.to eq 200 }
end
と書くと、describe
の内部にsubject do get "/users", {}
を追加してくれるイメージです。
結果として、subject
が呼び出されてそのHTTPステータスコードが200であることをテストします。
espec_request_describer
rubyに対して、rspecがあり、rspec-railsがあるのと同じように、elixirにはespecがあり、espec_phoenixがあります。
rspec-request_describerが好きなので、especでも同様のことがしたくなり、espec_request_describerを作りました。
サンプルコード
defmodule UserControllerSpec do
use ESpec.Phoenix, controller: UserController, async: true
use EspecRequestDescriber
describe_request "GET /users" do
it "returns 200" do
expect(subject.status) |> to(eq 200)
end
end
end
request_describe
を使ってリクエストを構築しています。
リクエストで返ってくるのは%Plug.Conn{}
です。
subject
でリクエストを飛ばして、その結果のステータスコードをテストしています。
パラメータ
リクエストにパラメータを渡したい時はlet :params
します。パラメータはキーワードリストであることに注意してください。
defmodule UserControllerSpec do
use ESpec.Phoenix, controller: UserController, async: true
describe_request "POST /user" do
let :params, do: [user: %{name: "hanamaru", email: "zura@gmail.com" }]
it "returns new user" do
subject.resp_body |> should(eq ~s({"data":{"email":"zura@gmail.com","name":"hanamaru","id":1}}))
end
end
end
conn
plugの仕様上、headersやその他のリクエストに関わる値はPlug.Conn
に入っています。
デフォルトではbuild_conn
が使われますが、sessionやheadersなどを書き換えたい時はlet :conn
でput_session
やput_req_header
などしてください。
埋め込み
rspec-request_describerと同様にパス中に:id
があると、let :id
で宣言したものを埋め込みます。:id
以外にも任意の変数名が使えるので、好きなように埋め込んで下さい。
良い方法かはともかく、user_path
などのヘルパーが使いたい時は
describe_request "GET :path" do
let :path, do: user_path(build_conn, :index)
it do: subject
end
してください。let :request_path
でも一応可能です。
rdescribe
especにはxdescribe
やfdescribe
のような関数があるようなので、アナロジーでrdescribe
にdescribe_request
のエイリアスを作ってあります。
知見
関数の動的呼び出し
埋め込みを行う際、文字列から関数を呼び出すためにapply/3
を使って、動的に関数(let
で宣言したもの)を呼び出しています。let
で宣言したものがどうなっているのか分かりませんが、なんかうまく動くので良しとします。
例えばespec_request_describerの内部ではパスに":id"
がある時は
apply(__MODULE__, :id, [])
みたいなことをしています。第一引数が呼ぶ関数のモジュール名、第二引数が関数名、第三引数が引数のリストです。
マクロの使い方
関数内のローカル変数を参照したい時はvar!
を使えばよいのは知っていましたが、マクロを呼び出した側のモジュールの関数の呼び出し、マクロを定義してある側のモジュールの関数の呼び出しがどうなるのかは知りませんでした。
結論としては、「ASTで構築したコードがそのまま展開される」というイメージで良いと思いました。つまり、マクロを呼び出した側のモジュールの関数は修飾なしで呼べ、マクロを定義してある側のモジュールの関数は修飾名で呼ぶ必要があります。
名前
モジュールの名前がEspecRequestDescriber
になっていますが、ESpec
に合わせてsを大文字にした方が良いのでしょうか・・・
help
英語力が低すぎてgithubのREADMEがあまり書けていません。添削してくださる方のPRを待っています。