在Sinatra中,如何测试可以return同时html和json的路由?
In Sinatra, how to test a route that can return both html and json?
我对 Sinatra 还是个新手,我的应用程序全部基于 json 构建,没有任何视图。现在我想有相同的行为,但在视图上呈现结果。当我刚刚 returning json 时,我的测试都成功了。现在我试图在路由中引入 erb 模板,我的测试崩溃了,路由方法中的变量也没有传递给视图。
我做错了什么?
测试代码如下:
main_spec.rb:
describe "when a player joins the game" do
it "welcomes the player" do
post "/join", "data" => '{"name": "Jon"}'
response = {status: Messages::JOIN_SUCCESS}
expect_response_to_eq(response)
end
it "sends an error message if no name is sent" do
post "/join", "data" => '{}'
response = {status: Messages::JOIN_FAILURE}
expect_response_to_eq(response)
end
it "sends an error message if player could not join the game" do
fill_the_game
post "/join", "data" => '{"name": "Jon"}'
response = {status: Messages::JOIN_FAILURE}
expect_response_to_eq(response)
end
it "returns an empty response if no data is sent" do
post "/join"
expect_response_to_eq({})
end
def expect_response_to_eq(response)
expect(last_response).to be_ok
expect(JSON.parse(last_response.body, symbolize_names: true)).to eq(response)
end
def fill_the_game
server.join_game("Jane")
server.join_game("Joe")
server.join_game("Moe")
server.join_game("May")
end
end
其中 Messages
是一个包含游戏字符串消息的模块。
我尝试测试的控制器方法最初看起来像这样,它只是 returned json 格式的响应:
main.rb
post "/join" do
response = helper.join_response(params)
@title = Messages::JOIN_TITLE
response.to_json
end
助手是一个 class,我在其中提取了所有业务逻辑,以便控制器只需处理 HTTP 请求。我使用依赖注入将助手传递给主控制器,这样更容易测试。
到目前为止,如果我 运行 测试,它们是绿色的。但是现在我想通过 erb 在视图中呈现响应的结果,同时仍然 returning json。所以我添加了这样的测试:
main_spec.rb:
it "renders the join page" do
h = {'Content-Type' => 'text/html'}
post "/join", "data" => '{"name": "Jon"}', "headers" => h
expect(last_response).to be_ok
expect(last_response.body).to include(Messages::JOIN_TITLE)
end
然后修改加入路由器使测试通过(为此我需要 sinatra-contrib
):
main.rb:
post "/join", :provides => ['html', 'json'] do
response = helper.join_response(params)
@title = Messages::JOIN_TITLE
@r_status = response[:status]
respond_to do |format|
format.html { erb :join }
format.json { response.to_json }
end
response.to_json
end
这打破了我所有的测试并显示消息:
Failure/Error: expect(last_response).to be_ok
expected `#<Rack::MockResponse:0x007f89e9a941e0...`
# ommited this part due to space constrains
`...@body=["<h1>Internal Server Error</h1>"]>.ok?` to return true, got false
所以我尝试了其他方法:
main.rb:
post "/join", :provides => ['html', 'json'] do
response = helper.join_response(params)
@title = Messages::JOIN_TITLE
@r_status = response[:status]
request.accept.each do |type|
case type.to_s
when 'text/html'
halt erb :join
when 'text/json'
halt response.to_json
end
end
end
也用不同的消息破坏了一切:
Failure/Error: expect(JSON.parse(last_response.body, symbolize_names: true)).to eq(response)
JSON::ParserError:
784: unexpected token at '*/*'
如果我在末尾添加一行,response.to_json
,就在关闭方法之前,除了视图测试的最后一行 expect(last_response.body).to include(Messages::JOIN_TITLE)
,我的测试都通过了。事实上,当我在浏览器中加载页面时,@title
似乎被发送到页面而不是 @r_status
由于某种原因。在 erb 视图中,我有 <p><%= @r_status %></p>
,所以它应该显示出来。标题在布局 erb 中呈现为 <h1><%= @title %></h1>
.
我已经打印了 @r_status
的值并且它是正确的,但是如果我从 when
块内部打印东西,就好像它永远不会碰到那些。
我做错了什么?
为什么 @r_status 没有在视图中呈现,为什么 when
块没有被命中?我怎样才能使控制器 return 同时成为 html 或 json 最重要的是,我该如何测试它?
更新:
我已经更新了第一个测试以发送特定的 headers:
it "welcomes the player" do
h = {"Content-Type" => "application/json", "Accept" => "application/json"}
post "/join", "data" => '{"name": "Jon"}', "headers" => h
response = {status: Messages::JOIN_SUCCESS}
expect_response_to_eq(response)
end
我运行就是那个测试
$ rspec spec/main_spec.rb:20
当我从控制器打印请求信息时,这是我得到的:
p request.content_type
application/x-www-form-urlencoded
p request.accept
[#<Sinatra::Request::AcceptEntry:0x007f86de1566b8 @entry="*/*", @type="*/*", @params={}, @q=1.0>]
p params
{"data"=>"{\"name\": \"Jon\"}", "headers"=>{"Content-Type"=>"application/json", "Accept"=>"application/json"}}
似乎它没有占用我的 headers... 所以当我 request.accept.each do |type| case type.to_s
它 returns */*
两者都不匹配 html 也不是 json,这就是为什么它从不 运行 when
语句中的代码。我应该使用我的参数在路由方法中手动重定向到 erb 或 json 吗?我该怎么做?
第一个测试失败是因为应用程序中发生错误,因此您需要检查日志以查看是什么触发了它。 (你是否正在加载 Sinatra-Contrib 的 RespondWith 模块,因为 respond_to
不是 Sinatra 提供的方法)。
第二个示例的错误是因为 header 测试和应用程序之间的期望不匹配。在应用程序中,您正在使用 request.accept.each
迭代请求发送到您的应用程序的 Accept
headers,默认情况下为 '/',但测试发送 Content-Type
的 header 不会触发条件 case 语句。
我发现了错误。正如我所怀疑的那样,问题出在 header 中。我没有正确发送它们。
根据 the docs,这是在测试中使用的 HTTP 方法的签名:
get '/path', params={}, rack_env={}
所以我的测试没有成功,因为第三个参数是 a rack environment variable 而我在那里传递了 headers:
h = {'Content-Type' => 'text/html'}
post "/join", "data" => '{"name": "Jon"}', "headers" => h
相反,我必须在调用该方法之前分别添加每个 header 调用 header(header_name, header_value)
。所以现在我的 JSON 测试看起来像这样:
it "welcomes the player" do
header "Accept", "application/json"
post "/join", {"name" => "Jon"}
response = {status: Messages::JOIN_SUCCESS}
expect_response_to_eq(response)
end
我的视图测试如下所示:
it "renders the join page" do
header "Accept", "text/html"
post "/join", {"name" => "Jon"}
expect(last_response).to be_ok
expect(last_response.body).to include(Messages::JOIN_TITLE)
end
我在 main.rb 中的生产代码有效:
post "/join", :provides => ['html', 'json'] do
response = helper.join_response(params)
@title = Messages::JOIN_TITLE
@r_status = response[:status]
request.accept.each do |type|
case type.to_s
when 'text/html'
halt erb :join
when 'application/json'
halt response.to_json
end
end
error 406
end
如果我打印 headers 我得到:
p headers
{"Content-Type"=>"application/json"} # for JSON
{"Content-Type"=>"text/html;charset=utf-8"} # for the views
{"Content-Type"=>"text/html;charset=utf-8"} # in the browser
如果我根据请求打印它们,我会得到:
p request.accept
# for JSON:
[#<Sinatra::Request::AcceptEntry:0x007fcf973ff480 @entry="application/json", @type="application/json", @params={}, @q=1.0>]
# for the views:
[#<Sinatra::Request::AcceptEntry:0x007f5791fe29d8 @entry="text/html", @type="text/html", @params={}, @q=1.0>]
# in the browser:
<Sinatra::Request::AcceptEntry:0x007f2fd5a0ee50 @entry="text/html", @type="text/html", @params={}, @q=1.0>
我对 Sinatra 还是个新手,我的应用程序全部基于 json 构建,没有任何视图。现在我想有相同的行为,但在视图上呈现结果。当我刚刚 returning json 时,我的测试都成功了。现在我试图在路由中引入 erb 模板,我的测试崩溃了,路由方法中的变量也没有传递给视图。
我做错了什么?
测试代码如下:
main_spec.rb:
describe "when a player joins the game" do
it "welcomes the player" do
post "/join", "data" => '{"name": "Jon"}'
response = {status: Messages::JOIN_SUCCESS}
expect_response_to_eq(response)
end
it "sends an error message if no name is sent" do
post "/join", "data" => '{}'
response = {status: Messages::JOIN_FAILURE}
expect_response_to_eq(response)
end
it "sends an error message if player could not join the game" do
fill_the_game
post "/join", "data" => '{"name": "Jon"}'
response = {status: Messages::JOIN_FAILURE}
expect_response_to_eq(response)
end
it "returns an empty response if no data is sent" do
post "/join"
expect_response_to_eq({})
end
def expect_response_to_eq(response)
expect(last_response).to be_ok
expect(JSON.parse(last_response.body, symbolize_names: true)).to eq(response)
end
def fill_the_game
server.join_game("Jane")
server.join_game("Joe")
server.join_game("Moe")
server.join_game("May")
end
end
其中 Messages
是一个包含游戏字符串消息的模块。
我尝试测试的控制器方法最初看起来像这样,它只是 returned json 格式的响应:
main.rb
post "/join" do
response = helper.join_response(params)
@title = Messages::JOIN_TITLE
response.to_json
end
助手是一个 class,我在其中提取了所有业务逻辑,以便控制器只需处理 HTTP 请求。我使用依赖注入将助手传递给主控制器,这样更容易测试。
到目前为止,如果我 运行 测试,它们是绿色的。但是现在我想通过 erb 在视图中呈现响应的结果,同时仍然 returning json。所以我添加了这样的测试:
main_spec.rb:
it "renders the join page" do
h = {'Content-Type' => 'text/html'}
post "/join", "data" => '{"name": "Jon"}', "headers" => h
expect(last_response).to be_ok
expect(last_response.body).to include(Messages::JOIN_TITLE)
end
然后修改加入路由器使测试通过(为此我需要 sinatra-contrib
):
main.rb:
post "/join", :provides => ['html', 'json'] do
response = helper.join_response(params)
@title = Messages::JOIN_TITLE
@r_status = response[:status]
respond_to do |format|
format.html { erb :join }
format.json { response.to_json }
end
response.to_json
end
这打破了我所有的测试并显示消息:
Failure/Error: expect(last_response).to be_ok
expected `#<Rack::MockResponse:0x007f89e9a941e0...`
# ommited this part due to space constrains
`...@body=["<h1>Internal Server Error</h1>"]>.ok?` to return true, got false
所以我尝试了其他方法:
main.rb:
post "/join", :provides => ['html', 'json'] do
response = helper.join_response(params)
@title = Messages::JOIN_TITLE
@r_status = response[:status]
request.accept.each do |type|
case type.to_s
when 'text/html'
halt erb :join
when 'text/json'
halt response.to_json
end
end
end
也用不同的消息破坏了一切:
Failure/Error: expect(JSON.parse(last_response.body, symbolize_names: true)).to eq(response)
JSON::ParserError:
784: unexpected token at '*/*'
如果我在末尾添加一行,response.to_json
,就在关闭方法之前,除了视图测试的最后一行 expect(last_response.body).to include(Messages::JOIN_TITLE)
,我的测试都通过了。事实上,当我在浏览器中加载页面时,@title
似乎被发送到页面而不是 @r_status
由于某种原因。在 erb 视图中,我有 <p><%= @r_status %></p>
,所以它应该显示出来。标题在布局 erb 中呈现为 <h1><%= @title %></h1>
.
我已经打印了 @r_status
的值并且它是正确的,但是如果我从 when
块内部打印东西,就好像它永远不会碰到那些。
我做错了什么?
为什么 @r_status 没有在视图中呈现,为什么 when
块没有被命中?我怎样才能使控制器 return 同时成为 html 或 json 最重要的是,我该如何测试它?
更新:
我已经更新了第一个测试以发送特定的 headers:
it "welcomes the player" do
h = {"Content-Type" => "application/json", "Accept" => "application/json"}
post "/join", "data" => '{"name": "Jon"}', "headers" => h
response = {status: Messages::JOIN_SUCCESS}
expect_response_to_eq(response)
end
我运行就是那个测试
$ rspec spec/main_spec.rb:20
当我从控制器打印请求信息时,这是我得到的:
p request.content_type
application/x-www-form-urlencoded
p request.accept
[#<Sinatra::Request::AcceptEntry:0x007f86de1566b8 @entry="*/*", @type="*/*", @params={}, @q=1.0>]
p params
{"data"=>"{\"name\": \"Jon\"}", "headers"=>{"Content-Type"=>"application/json", "Accept"=>"application/json"}}
似乎它没有占用我的 headers... 所以当我 request.accept.each do |type| case type.to_s
它 returns */*
两者都不匹配 html 也不是 json,这就是为什么它从不 运行 when
语句中的代码。我应该使用我的参数在路由方法中手动重定向到 erb 或 json 吗?我该怎么做?
第一个测试失败是因为应用程序中发生错误,因此您需要检查日志以查看是什么触发了它。 (你是否正在加载 Sinatra-Contrib 的 RespondWith 模块,因为 respond_to
不是 Sinatra 提供的方法)。
第二个示例的错误是因为 header 测试和应用程序之间的期望不匹配。在应用程序中,您正在使用 request.accept.each
迭代请求发送到您的应用程序的 Accept
headers,默认情况下为 '/',但测试发送 Content-Type
的 header 不会触发条件 case 语句。
我发现了错误。正如我所怀疑的那样,问题出在 header 中。我没有正确发送它们。
根据 the docs,这是在测试中使用的 HTTP 方法的签名:
get '/path', params={}, rack_env={}
所以我的测试没有成功,因为第三个参数是 a rack environment variable 而我在那里传递了 headers:
h = {'Content-Type' => 'text/html'}
post "/join", "data" => '{"name": "Jon"}', "headers" => h
相反,我必须在调用该方法之前分别添加每个 header 调用 header(header_name, header_value)
。所以现在我的 JSON 测试看起来像这样:
it "welcomes the player" do
header "Accept", "application/json"
post "/join", {"name" => "Jon"}
response = {status: Messages::JOIN_SUCCESS}
expect_response_to_eq(response)
end
我的视图测试如下所示:
it "renders the join page" do
header "Accept", "text/html"
post "/join", {"name" => "Jon"}
expect(last_response).to be_ok
expect(last_response.body).to include(Messages::JOIN_TITLE)
end
我在 main.rb 中的生产代码有效:
post "/join", :provides => ['html', 'json'] do
response = helper.join_response(params)
@title = Messages::JOIN_TITLE
@r_status = response[:status]
request.accept.each do |type|
case type.to_s
when 'text/html'
halt erb :join
when 'application/json'
halt response.to_json
end
end
error 406
end
如果我打印 headers 我得到:
p headers
{"Content-Type"=>"application/json"} # for JSON
{"Content-Type"=>"text/html;charset=utf-8"} # for the views
{"Content-Type"=>"text/html;charset=utf-8"} # in the browser
如果我根据请求打印它们,我会得到:
p request.accept
# for JSON:
[#<Sinatra::Request::AcceptEntry:0x007fcf973ff480 @entry="application/json", @type="application/json", @params={}, @q=1.0>]
# for the views:
[#<Sinatra::Request::AcceptEntry:0x007f5791fe29d8 @entry="text/html", @type="text/html", @params={}, @q=1.0>]
# in the browser:
<Sinatra::Request::AcceptEntry:0x007f2fd5a0ee50 @entry="text/html", @type="text/html", @params={}, @q=1.0>