Edited at

espec_request_describerを作った

More than 1 year has passed since last update.


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 :connput_sessionput_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にはxdescribefdescribeのような関数があるようなので、アナロジーでrdescribedescribe_requestのエイリアスを作ってあります。


知見


関数の動的呼び出し

埋め込みを行う際、文字列から関数を呼び出すためにapply/3を使って、動的に関数(letで宣言したもの)を呼び出しています。letで宣言したものがどうなっているのか分かりませんが、なんかうまく動くので良しとします。

例えばespec_request_describerの内部ではパスに":id"がある時は

apply(__MODULE__, :id, [])

みたいなことをしています。第一引数が呼ぶ関数のモジュール名、第二引数が関数名、第三引数が引数のリストです。


マクロの使い方

関数内のローカル変数を参照したい時はvar!を使えばよいのは知っていましたが、マクロを呼び出した側のモジュールの関数の呼び出し、マクロを定義してある側のモジュールの関数の呼び出しがどうなるのかは知りませんでした。

結論としては、「ASTで構築したコードがそのまま展開される」というイメージで良いと思いました。つまり、マクロを呼び出した側のモジュールの関数は修飾なしで呼べ、マクロを定義してある側のモジュールの関数は修飾名で呼ぶ必要があります。


名前

モジュールの名前がEspecRequestDescriberになっていますが、ESpecに合わせてsを大文字にした方が良いのでしょうか・・・


help

英語力が低すぎてgithubのREADMEがあまり書けていません。添削してくださる方のPRを待っています。