8

Is it possible to cover my controller, that is highly depeinding on Etags with unit tests?

Here's what i'm trying to do: in case if page is not stale (meaning that it's fresh), i'm adding some header to response.

When i'm trying to test it all (rspec), no matter how many similar requests i have, i still receive 200 OK instead of 304, and my header doesn't get modified. Furthermore, if i track request.fresh?(response), it's ALWAYS false.

However, it perfectly works in browser. I've already tried to state ActionController::Base.perform_caching = true, it doesn't change the overall situation.

Thank you

0100110010101
  • 6,469
  • 5
  • 33
  • 38

6 Answers6

11

Here's how you can test if second request returns 304 response:

    get action, params
    assert_response 200, @response.body
    etag = @response.headers["ETag"]
    @request.env["HTTP_IF_NONE_MATCH"] = etag
    get action, params
    assert_response 304, @response.body
szeryf
  • 3,197
  • 3
  • 27
  • 28
5

Rails hashes the :etag you provide:

headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")

so setting something simple as

frash_when(:etag => 'foo')

would only be triggered by the right digest (the double quotes are necessary)

def with_etag
  if stale?(:etag => 'foo')
    render :text => 'OK'
  end
end

... tested by ...

@request.env['HTTP_IF_NONE_MATCH'] = '"acbd18db4cc2f85cedef654fccc4a4d8"'
get :with_etag
assert_equal 304, @response.status.to_i

same for modified:

def with_modified
  if stale?(:last_modified => 1.minute.ago)
    render :text => 'OK'
  end
end

... tested by ...

@request.env['HTTP_IF_MODIFIED_SINCE'] = 2.minutes.ago.rfc2822
get :with_modified
assert_equal 304, @response.status.to_i
grosser
  • 14,707
  • 7
  • 57
  • 61
  • oh, that was a question from ages ago. i think it's still been rails 2.3.5, so things may have changed a lot since then. – 0100110010101 Nov 23 '11 at 12:10
4

Ok, here's a point:

Before hitting the request, read up everything that's related to ETags in Rails code and Don't forget to set:

request.env["HTTP_IF_MODIFIED_SINCE"]
request.env["HTTP_IF_NONE_MATCH"]

Since they're required for ETag testing.

fotanus
  • 19,618
  • 13
  • 77
  • 111
0100110010101
  • 6,469
  • 5
  • 33
  • 38
  • Hi, could you explain in detail what that means? When i set the request.env it still does not contain the etag. – Tosa Mar 05 '13 at 01:28
  • @Tosa please see my answer below for some ideas (I couldn't post so much code in a comment). – szeryf Mar 11 '13 at 21:10
  • 1
    How is this an answer, let alone the accepted answer? You say do some research and set some env variables, but you don't say what to research and what to set the env variables to. – d_rail Dec 29 '17 at 19:55
1

This gist is very useful re etag testing in rspec -

https://gist.github.com/brettfishman/3868277

martinl
  • 1,550
  • 1
  • 8
  • 10
1

At least in Rails 5.2, szeryf's solution fails. This variation does work:

get action, parms
assert_response 200, @response.code
etag = @response.headers["ETag"]
get action, parms, headers: { "HTTP_IF_NONE_MATCH": etag }
assert_response 304, @response.code

See Rails Guides: https://guides.rubyonrails.org/testing.html#setting-headers-and-cgi-variables

dpneumo
  • 131
  • 1
  • 6
0

Rails 4.2 now also takes in to account the digest of the template. For me the following worked:

def calculate_etag(record, template)
  Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key([
    record,
    controller.send(:lookup_and_digest_template, template)
  ])).inspect
end

def set_cache_headers(modified_since: nil, record: nil, template: nil)
  request.if_modified_since = modified_since.rfc2822
  request.if_none_match = calculate_etag(record, template)
end

set_cache_headers(
  modified_since: 2.days.ago,
  record: @book,
  template: 'books/index'
)