How to test rails ETag caching?
-
27-09-2019 - |
Question
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
Solution 3
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.
OTHER TIPS
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
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
This gist is very useful re etag testing in rspec -
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'
)
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