Permalink
Browse files

Action cache testing. See with_caching and cache_action.

  • Loading branch information...
1 parent 34f177e commit 5a46ebd0d5a08ee1283b5b8c50f00a7d99478ac2 @aiwilliams aiwilliams committed Mar 11, 2009
View
@@ -1,4 +1,4 @@
-Copyright (c) 2007, Adam Williams and John W. Long.
+Copyright (c) 2007-2009, Adam Williams and John W. Long.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -16,4 +16,8 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+SOFTWARE.
+
+
+Cache testing portions taken from http://github.com/avdgaag/rspec-caching-test-plugin
+Copyright (c) 2008 Arjan van der Gaag, released under the MIT license
View
@@ -1,11 +1,15 @@
require 'spec'
require 'spec/rails'
-require 'spec/integration/extensions'
+require 'spec/integration/extensions/action_controller/base'
+require 'spec/integration/extensions/action_controller/caching'
+require 'spec/integration/extensions/hash'
require 'spec/integration/matchers'
require 'spec/integration/dsl'
require 'spec/integration/example/integration_example_group'
module Spec # :nodoc:
module Integration # :nodoc:
end
-end
+end
+
+ActionController::Base.cache_store = Spec::Integration::Extensions::ActionController::Caching::TestStore.new
@@ -1,3 +1,4 @@
+require 'spec/integration/dsl/caching'
require 'spec/integration/dsl/navigation'
require 'spec/integration/dsl/form'
@@ -0,0 +1,21 @@
+module Spec
+ module Integration
+ module DSL
+
+ # Provides a context within which view caching is enabled across
+ # requests made within the block.
+ #
+ def with_caching
+ prior_perform_caching = ActionController::Base.perform_caching
+ ActionController::Base.perform_caching = true
+ ActionController::Base.cache_store.reset
+ ActionController::Base.cache_store.read_cache = true
+ yield
+ ensure
+ ActionController::Base.cache_store.read_cache = false
+ ActionController::Base.perform_caching = prior_perform_caching
+ end
+
+ end
+ end
+end
@@ -1,8 +0,0 @@
-Dir[File.dirname(__FILE__) + '/extensions/*'].each { |f| require f }
-
-module Spec
- module Integration
- module Extensions # :nodoc:
- end
- end
-end
@@ -0,0 +1,154 @@
+module Spec
+ module Integration
+ module Extensions
+ module ActionController
+ module Caching
+
+ module ClassMethods #:nodoc:
+ def cache_page(content, path)
+ test_page_cached << path
+ end
+
+ def expire_page(path)
+ test_page_expired << path
+ end
+
+ def cached?(path)
+ test_page_cached.include?(path)
+ end
+
+ def expired?(path)
+ test_page_expired.include?(path)
+ end
+
+ def reset_page_cache!
+ test_page_cached.clear
+ test_page_expired.clear
+ end
+ end
+
+ module InstanceMethods
+ # See if the page caching mechanism has cached a given url. This takes
+ # the same options as +url_for+.
+ def cached?(options = {})
+ self.class.cached?(test_cache_url(options))
+ end
+
+ # See if the page caching mechanism has expired a given url. This
+ # takes the same options as +url_for+.
+ def expired?(options = {})
+ self.class.expired?(test_cache_url(options))
+ end
+
+ private
+ def test_cache_url(options) #:nodoc:
+ url_for(options.merge({ :only_path => true, :skip_relative_url_root => true }))
+ end
+ end
+
+ # == Perform the actual caching
+ #
+ # This test cache store can actually cache content, but by default
+ # does not. If it caches and returns cached content this might affect
+ # your tests, forcing you to reset the cache for every test so as to
+ # get the desired behaviour.
+ #
+ # The default behaviour is set on creation of the TestStore by
+ # passing in a simple flag. You can, however, change this at run time
+ # like so:
+ #
+ # @a = TestStore.new # => caching is off
+ # @a.read_cache = true # => caching is on
+ #
+ # @b = TestStore.new(true) # => caching is on
+ # @b.read_cache = false # => caching is off
+ #
+ # When needed the cache can be cleared manually like so:
+ #
+ # ActionController::Base.cache_store.reset
+ #
+ class TestStore < ActiveSupport::Cache::Store
+
+ # Record of what the app tells us to cache
+ attr_reader :cached
+
+ # Record of what the app tells us to expire
+ attr_reader :expired
+
+ # Record of what the app tells us to expire via patterns
+ attr_reader :expiration_patterns
+
+ # Cached data that could be returned
+ attr_reader :data
+
+ # Setting to enable the returning of cached data.
+ attr_accessor :read_cache
+
+ def initialize(do_read_cache = false) #:nodoc:
+ @data = {}
+ @cached = []
+ @expired = []
+ @expiration_patterns = []
+ @read_cache = do_read_cache
+ end
+
+ # Reset the cache store, effectively emptying the cache
+ def reset
+ @data.clear
+ @cached.clear
+ @expired.clear
+ @expiration_patterns.clear
+ end
+
+ def read(name, options = nil) #:nodoc:
+ super
+ read_cache ? @data[name] : nil
+ end
+
+ def write(name, value, options = nil) #:nodoc:
+ super
+
+ # Actually store the data if desired
+ @data[name] = value if read_cache
+
+ # Record this caching
+ @cached << name
+ end
+
+ def delete(name, options = nil) #:nodoc:
+ super
+ @expired << name
+ end
+
+ def delete_matched(matcher, options = nil) #:nodoc:
+ super
+ @expiration_patterns << matcher
+ end
+
+ # See if a given name was written to the cache
+ def cached?(name)
+ @cached.include?(name)
+ end
+
+ # See if a given name was expired from the cache, eiter directly or
+ # using an expiration pattern.
+ def expired?(name)
+ @expired.include?(name) || @expiration_patterns.detect { |matcher| name =~ matcher }
+ end
+ end
+
+ end
+ end
+ end
+ end
+end
+
+ActionController::Base.module_eval do
+ include Spec::Integration::Extensions::ActionController::Caching::InstanceMethods
+ extend Spec::Integration::Extensions::ActionController::Caching::ClassMethods
+
+ @@test_page_cached = [] # keep track of what gets cached
+ @@test_page_expired = [] # keeg track of what gets expired
+ cattr_accessor :test_page_cached
+ cattr_accessor :test_page_expired
+end
@@ -0,0 +1,135 @@
+module Spec
+ module Integration
+ module Matchers
+
+ class CacheAction #:nodoc:
+ def initialize(name, controller_context)
+ @name = name
+ @controller_context = controller_context
+ end
+
+ # Call the block of code passed to this matcher and see if
+ # our action has been written to the cache.
+ #
+ # We determine the +fragment_cache_key+ here, taking the effort to
+ # pass in the controller to this class, because this method only
+ # works in the context of a request. Calling the block gives us that
+ # request.
+ def matches?(block)
+ ActionController::Base.cache_store.reset
+ block.call
+ @key = @name.is_a?(String) ? @name : @controller_context.controller.fragment_cache_key(@name)
+ return ActionController::Base.cache_store.cached?(@key)
+ end
+
+ def failure_message
+ reason = if ActionController::Base.cache_store.cached.any?
+ "the cache only has #{ActionController::Base.cache_store.cached.to_yaml}."
+ else
+ "the cache is empty."
+ end
+ "Expected block to cache action #{@name.inspect} (#{@key}), but #{reason}"
+ end
+
+ def negative_failure_message
+ "Expected block not to cache action #{@name.inspect} (#{@key})"
+ end
+ end
+
+ # See if an action gets cached
+ #
+ # Usage:
+ #
+ # lambda { get :index }.should cache_action(:index)
+ #
+ # You can pass in the name of an action which will then get
+ # interpreted in the context of the current controller. Alternatively,
+ # you can pass in a whole +Hash+ for +url_for+ defining all your
+ # paramaters.
+ def cache_action(action)
+ action = { :action => action } unless action.is_a?(Hash)
+ CacheAction.new(action, self)
+ end
+
+ # See if a fragment gets cached.
+ #
+ # The name you pass in can be any name you have given your fragment.
+ # This would typically be a +String+.
+ #
+ # Usage:
+ #
+ # lambda { get :index }.should cache('my_caching')
+ #
+ def cache(name)
+ CacheAction.new(name, self)
+ end
+ alias_method :cache_fragment, :cache
+
+ class ExpireAction #:nodoc:
+ def initialize(name, controller_context)
+ @name = name
+ @controller_context = controller_context
+ end
+
+ # Call the block of code passed to this matcher and see if
+ # our action has been removed from the cache.
+ #
+ # We determine the +fragment_cache_key+ here, taking the effort to
+ # pass in the controller to this class, because this method only
+ # works in the context of a request. Calling the block gives us that
+ # request.
+ def matches?(block)
+ ActionController::Base.cache_store.reset
+ block.call
+ @key = @name.is_a?(String) ? @name : @controller_context.controller.fragment_cache_key(@name)
+ return ActionController::Base.cache_store.expired?(@key)
+ end
+
+ def failure_message
+ reason = if ActionController::Base.cache_store.expired.any?
+ "the cache has only expired #{ActionController::Base.cache_store.expired.to_yaml}."
+ else
+ "nothing was expired."
+ end
+ "Expected block to expire action #{@name.inspect} (#{@key}), but #{reason}"
+ end
+
+ def negative_failure_message
+ "Expected block not to expire #{@name.inspect} (#{@key})"
+ end
+ end
+
+ # See if an action is expired
+ #
+ # Usage:
+ #
+ # lambda { get :index }.should expire_action(:index)
+ #
+ # You can pass in the name of an action which will then get
+ # interpreted in the context of the current controller. Alternatively,
+ # you can pass in a whole +Hash+ for +url_for+ defining all your
+ # paramaters.
+ #
+ # This is a shortcut method to +expire+.
+ def expire_action(action)
+ action = { :action => action } unless action.is_a?(Hash)
+ expire(action)
+ end
+
+ # See if a fragment is expired
+ #
+ # The name you pass in can be any name you have given your fragment.
+ # This would typically be a +String+.
+ #
+ # Usage:
+ #
+ # lambda { get :index }.should expire('my_cached_something')
+ #
+ def expire(name)
+ ExpireAction.new(name, self)
+ end
+ alias_method :expire_fragment, :expire
+
+ end
+ end
+end
View
@@ -0,0 +1,28 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+
+describe 'caches_action', :type => :integration do
+ it 'should work' do
+ time_one = Time.local(2009,1,1)
+ time_two = Time.local(2009,1,2)
+
+ Time.stub!(:now).and_return(time_two)
+ get '/caching_action'
+ response.body.should == time_two.to_s
+
+ with_caching do
+ # Let us ensure that it is not cached from outside this block
+ Time.stub!(:now).and_return(time_one)
+ get '/caching_action'
+ response.body.should == time_one.to_s
+
+ # And now it is cached here
+ Time.stub!(:now).and_return(time_two)
+ get '/caching_action'
+ response.body.should == time_one.to_s
+ end
+
+ # And now we should get fresh content
+ get '/caching_action'
+ response.body.should == time_two.to_s
+ end
+end
Oops, something went wrong.

0 comments on commit 5a46ebd

Please sign in to comment.