Rails の functionals なテストで JSON を POST/PUT する方法

Rails4 で、JSON を受け取る API を作りました。
早速、テスト書くぞー!と意気込んだものの、はて。JSON ってどうやって投げればいいんだろう。
いろいろ検索して試してみたものの、うまくいくまで時間がかかったので、メモ。

うまくいったやり方

put :update, user: { name: 'HOGE', mofu: 333, moffu: { mofu: 12, bar: true, foo: 'HI' } }, foobar: true, barfoo: { mofu: 3 }, format: :json                                            
assert_equal( 'HOGE', User.first.name ) 

# params => {"user"=>{"name"=>"HOGE", "mofu"=>333, "moffu"=>{"mofu"=>12, "bar"=>true, "foo"=>"HI"}}, "foobar"=>true, "barfoo"=>{"mofu"=>3}, "format"=>"json", "controller"=>"api/user", "action"=>"update"}

ポイント

  • POST するオブジェクトを { } でくくってはいけない。
  • 最後の引数に format: :json を書く。

ダメだった例1 第2引数を { } でくくる

put :update, { user: { name: 'HOGE', mofu: 333, moffu: { mofu: 12, bar: true, foo: 'HI' } }, foobar: true, barfoo: { mofu: 3 } }, format: :json
assert_equal( 'HOGE', User.first.name )

# params => {"user"=>{"name"=>"HOGE", "mofu"=>"333", "moffu"=>{"mofu"=>"12", "bar"=>true, "foo"=>"HI"}}, "foobar"=>true, "barfoo"=>{"mofu"=>"3"}, "controller"=>"api/user", "action"=>"update"}

一見、うまくいっているように見えるが、何故か数字だけが文字列になってしまっています。
JSONのソースを見てみたりと、いろいろ調べたものの、何故か分からず....

ダメだった例2 JSON文字列で渡してみる。

put :update, 'user: { name: 'HOGE', mofu: 333, moffu: { mofu: 12, bar: true, foo: 'HI' } }, foobar: true, barfoo: { mofu: 3 }', :type => :json
assert_equal( 'HOGE', User.first.name )

# params => {"type"=>"json", "controller"=>"api/user", "action"=>"update"}

渡らない。

ダメだった例3 タイプだけ変えてみる。

put :update, user: { name: 'HOGE', mofu: 333, moffu: { mofu: 12, bar: true, foo: 'HI' } }, foobar: true, barfoo: { mofu: 3 }, :type => :json
assert_equal( 'HOGE', User.first.name )

# params => {"user"=>{"name"=>"HOGE", "mofu"=>"333", "moffu"=>{"mofu"=>"12", "bar"=>true, "foo"=>"HI"}}, "foobar"=>true, "barfoo"=>{"mofu"=>"3"}, "controller"=>"api/user", "action"=>"update"}

数値が文字列になる。

ダメだった例4 最後の引数を消す

put :update, user: { name: 'HOGE', mofu: 333, moffu: { mofu: 12, bar: true, foo: 'HI' } }, foobar: true, barfoo: { mofu: 3 }
assert_equal( 'HOGE', User.first.name )

# params => {"user"=>{"name"=>"HOGE", "mofu"=>"333", "moffu"=>{"mofu"=>"12", "bar"=>true, "foo"=>"HI"}}, "foobar"=>true, "barfoo"=>{"mofu"=>"3"}, "controller"=>"api/user", "action"=>"update"}

やはり、数字が文字列になってしまう。

ダメだった例5 直接指定

@request.env["RAW_POST_DATA"] = { user: { name: 'HOGE', mofu: 333, moffu: { mofu: 12, bar: true, foo: 'HI' } }, foobar: true, barfoo: { mofu: 3 } }.to_json
@request.env["CONTENT_TYPE"] = 'application/json'
put :update
assert_equal( 'HOGE', User.first.name )

# params => {"controller"=>"api/user", "action"=>"update"}

パラメータが渡らない。

まとめ

Rails の functionals なテストで JSON を POST するには、以下の2点を守ればいいらしい。

  • {}でくくらずにオブジェクトを引数で指定する。
  • 最後の引数に format: :json を指定する。

上記の方法を使うと、rake test でも RSpec でもちゃんと POST できました。