For the new version of iSearch, I used the page caching feature of Ruby on Rails a lot. I also needed to refresh those pages when some model where added, updated or deleted, so I needed to use cache sweeping too.

Now caching is great, but you need to be sure it works, and what's the best thing you can do to make sure it works? Write tests! But here we have a problem, as I could not find any information about page caching testing. So I decided to add some page caching test helpers to my application, and while I'm at it, released them as a plugin.

Here, I will explain how the plugin is working. Go to the plugin page if you want more information about how to download, install and use it.

First let's have a look at the init.rb file:

1
2
3
4
5
6
7

if RAILS_ENV == "test"
  require 'page_cache_test'

  Test::Unit::TestCase.class_eval do
    include Cosinux::PageCacheTest
  end
end

On line 1, we make sure that we are in the test environment so we don't interfere with other environments. If it is the case, we first load the page_cache_test.rb file on line 2. Then we include the module Cosinux::PageCacheTest in the Test::Unit::TestCase on line 4 to 6.

Now let's go in the page_cache_test.rb and see what happens when the Cosinux::PageCacheTest module is included.

1
2
3
4
5
6

module Cosinux::PageCacheTest
  def self.included(base)
    ActionController::Base.public_class_method :page_cache_path
    ActionController::Base.perform_caching = true
  end
end

The method self.included on line two, defined in the Module class is executed each time a module is included. So here it is executed because of the include we've just seen in the init.rb file.

In this method, on line 3, we change the visibility of the method page_cache_path of ActionController::Base so that we can use it in our assertions. The method is used to get the path of the cached file for a url.

Then, on line 4, we ensure that caching is enabled (it is normally disabled in the test environment).

So were are we now ? We included the Cosinux::PageCacheTest module in the Test::Units::TestCase class so each method defined in the module is available in the class. There are two instance methods defined in the module:

  • assert_cache,
  • and assert_expire.

Let's have a look at both of them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

def assert_cache(*urls)
  silence do
    urls.each do |url|
      ActionController::Base.expire_page(url)
    end
  end

  if block_given?
    yield *urls
  else
    urls.each { |url| get url }
  end

  urls.each do |url|
    assert_block("#{url.inspect} is not " +
                         "cached after executing block") do
      File.exists? page_cache_path(url)
    end
  end
end

First, on line 3 to 5, we ensure that the urls we are checking are not in cache by expiring them on line 4. We also make sure that no message is logged by using the silence method which is defined at the end of the file.

Then, on line 8 and 9, if a block is given, we execute it, giving it the urls as arguments. Otherwise, on line 11, we execute a get request on each of the given urls.

Finaly, on line 14 to 18, we make sure that the urls are cached by checking that the corresponding files exists on the file system.

Now to the other method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

def assert_expire(*urls)
  silence do
    urls.each do |url|
      ActionController::Base.cache_page("testing", url)
    end
  end

  yield *urls

  urls.each do |url|
    assert_block("#{url.inspect} is cached" +
                         " after executing block") do
      ! File.exists? page_cache_path(url)
    end
  end
end

As you can see, it looks quite the same, excepts a few things.

First it makes sure that the urls are cached by writing "testing" in the corresponding files on line 4.

Then we always execute a block, so that the user can take some actions that should make the urls expire, like triggering an action which defines a sweeper.

And finally, on line 12, we assert that the files corresponding to the urls do not exists anymore.

That's it. I hope this will explain how this small plugin work and that it will help somebody. Next time, I will write about my experiments with a plugin to create multiple page forms.