mercredi 29 juin 2016

In Sinatra, how to test a route that can return both html and json?

I am still new to Sinatra and I have built my app all based in json, with no views. Now I would like to have the same behaviour but rendering the results on a view. When I was just returning json, my tests all worked. Now I am trying to introduce the erb templates in the routes, and my tests crash, and the variables in the route method don't get passed to the view either.

What am I doing wrong?

Here is the code of the tests:

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

where Messages is a module that contains string messages for the game.

My controller initially looked like this, it just returned the response in json format:

main.rb

post "/join" do
  response = helper.join_response(params)
  @title   = Messages::JOIN_TITLE
  response.to_json
end

The helper is a class where I extracted all the business logic so that the controller only has to deal with HTTP requests. I use dependency injection to pass the helper to the main controller, so that it is easier to test.

So up to here, if I run the tests, they are green. But now I want to render the results of the response in the views through erb, while still returning the json. So I added a test like this:

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

And then modified the join router to make the test pass:

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

This broke all my tests. So I tried something else:

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

Brokes everything as well.

If I add a line at the end, response.to_json, just before closing the method, my tests pass except for the last line expect(last_response.body).to include(Messages::JOIN_TITLE). Indeed when I load the page in a browser, the @title seems to be sent to the page but not the @r_status for some reason. In the erb view, I have <p><%= @r_status %></p>, so it should show up. The title is rendered in the layout erb, as <h1><%= @title %></h1>.

I have printed the value of @r_status and it is correct, but if I print stuff from inside the when blocks, it's like it never hits those.

What is it that I am doing wrong? Why is the @r_status not rendered in the view and why aren't the when blocks hit?

Aucun commentaire:

Enregistrer un commentaire