How to use HTTP status code symbols in RSpec?

25,593

Solution 1

On the one hand, response is built with methods like:

  • success?

  • redirect?

  • unprocessable?

  • full list do: response.methods.grep(/\?/)

On the other hand, Rspec predicates transforms every foo? method to a be_foo matcher.

Not sure you can have the 201 this way unfortunately, but creating a custom matcher is quite easy.

Note Rails test only rely on a few statuses.

Solution 2

The response object responds to several of the symbol types as messages. So you can simply do:

expect(response).to be_success
expect(response).to be_error
expect(response).to be_missing
expect(response).to be_redirect

For the other types, such as :created, you can create a simple custom matcher for this which wraps assert_response:

RSpec::Matchers.define :have_status do |type, message = nil|
  match do |_response|
    assert_response type, message
  end
end

expect(response).to have_status(:created)
expect(response).to have_status(404)

This should work fine for controller specs which have the proper state setup. It will not work for feature specs. I haven't tried with request specs, so your milage may vary there.

The reason this works is it leverages the fact that RSpec controller specs have similar state setup behind the scenes. So when assert_response accesses @response it is available.

This matcher can probably be improved by simply copying the code used by assert_response into the matcher:

RSpec::Matchers.define :have_status do |type, message = nil|
  match do |response|
    if Symbol === type
      if [:success, :missing, :redirect, :error].include?(type)
        response.send("#{type}?")
      else
        code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
        response.response_code == code
      end
    else
      response.response_code == type
    end
  end

  failure_message do |response|
    message or
      "Expected response to be a <#{type}>, but was <#{response.response_code}>"
  end
end

UPDATE: 2014-07-02

This is now available out of the box with RSpec Rails 3: https://www.relishapp.com/rspec/rspec-rails/v/3-0/docs/matchers/have-http-status-matcher

Solution 3

this works for me:

expect(response.response_code).to eq(Rack::Utils::SYMBOL_TO_STATUS_CODE[:not_found])

Solution 4

With rspec-rails (as of rspec 3) it's possible to use

expect(response).to have_http_status(:created)

Update 2018-06-11:

As of Rails 6, some of the matchers will be replaced (e. g. success by successful).

Share:
25,593
JJD
Author by

JJD

Android, Kotlin, Java, Git, Python, Ruby, Ruby on Rails, JavaScript, MacOS, Ubuntu #SOreadytohelp http://stackoverflow.com/10m

Updated on July 15, 2022

Comments

  • JJD
    JJD almost 2 years

    I use HTTP status code symbols in code in a controller such as:

    render json: {
        auth_token: user.authentication_token, 
        user: user
      }, 
      status: :created
    

    or

    render json: {
        errors: ["Missing parameter."]
      }, 
      success: false, 
      status: :unprocessable_entity
    

    In the code of my request spec I also would like to use the symbols:

    post user_session_path, email: @user.email, password: @user.password
    expect(last_response.status).to eq(201)
    

    ...

    expect(last_response.status).to eq(422)
    

    However each test where I use the symbols instead of integers fails:

    Failure/Error: expect(last_response.status).to eq(:created)
    
      expected: :created
           got: 201
    
      (compared using ==)
    

    Here is the latest list of HTTP status code symbols in Rack.

  • JJD
    JJD over 10 years
    I tried expect(last_response.status).to be_created which fails with "undefined method `created?' for 201:Fixnum".
  • apneadiving
    apneadiving over 10 years
    expect(last_response).to be_success
  • JJD
    JJD over 10 years
    Same: expect(last_response.status).to be_success fails with "undefined method 'success?' for 201:Fixnum". I understood that created? is not part of the list and experimented with a custom matcher ... w/o success by now though.
  • apneadiving
    apneadiving over 10 years
    reread what I wrote: expect(last_response) not expect(last_response.status)
  • JJD
    JJD over 10 years
    I admin I did not read your comments so well but this time it fails with "undefined method `success?' for #<Rack::MockResponse:0x000000058c1180>"
  • apneadiving
    apneadiving over 10 years
    why do you have a <Rack::MockResponse:0x000000058c1180> it should be a ActionController::TestResponse. where do your specs live?
  • JJD
    JJD over 10 years
    The request test I am executing is located in spec/api/sessions_controller_spec.rb.
  • apneadiving
    apneadiving over 10 years
    request spec? request specs should live in spec/features, controller spec should live in spec/controllers, otherwise the wrong modules will be added (unless you include them manually)
  • JJD
    JJD over 10 years
    I might misunderstand but rspec-rails states that request specs belong either in spec/requests, spec/api or spec/integration.
  • apneadiving
    apneadiving over 10 years
    from your link: Note that Capybara's DSL as shown is, by default, only available in specs in the spec/features directory
  • JJD
    JJD over 10 years
    Thanks. But when I move the test into spec/features it fails with undefined local variable or method 'app' for #<RSpec::Core::ExampleGroup::Neste... The spec_helper.rb contains config.include Rack::Test::Methods, type: :feature meanwhile. See my updated question now including post user_session_path ....
  • apneadiving
    apneadiving over 10 years
    in a feature spec you should not bother about response, you should work on page. response is for controllers spec.
  • apneadiving
    apneadiving over 10 years
    I dont what and how you did but when you do not follow the standard paths, it's normal you stumble upon many issues/
  • JJD
    JJD over 10 years
    I guess I am on the wrong way. Can you point to the correct path please to test the response of my API endpoints?
  • apneadiving
    apneadiving over 10 years
    This shoul be controller specs, and you'd be able to use what I put in my answer
  • JJD
    JJD over 10 years
    I am trying to follow your advise and tutorials like this but I still can't get it running. To avoid further drifting away from my original question here is a new post.
  • apneadiving
    apneadiving over 10 years
    I've spent much time on your question without any counterpart, don't expect me to spend more elsewhere. sorry.
  • JJD
    JJD over 10 years
    No offense please. I am the last one who forgets to vote on people's help. Though, normally I wait until I the problem is solved in total before I vote or grant the answer flag.
  • Tinynumbers
    Tinynumbers almost 9 years
    Only in RSpec version 3 or later. have_http_status is not included in RSpec 2.