0
votes

I've got a Rails 3.2.21 app on Ruby 2.1.5 that uses Postgres, Redis as the cache store (config.cache_store = :redis_store), background workers (mostly for view cache warming) with sidekiq. Russian doll caching used with the cache_digests gem so you end up with cache keys like views/my_lovely_partial/5506949e3754753ad58190924d5b029f. Running tests with RSpec, Factory Girl and Capybara.

For the test environment I have set up a parallel Redis server, different port to production and dev, and have "config.action_controller.perform_caching = true" in test rb. It's the same redis setup in dev and test apart from the port being different. Testing through either controller spec or feature spec I see the presence of objects cached in Redis, either through the tests themselves or directly viewing the keys in redis-cli.

When I try to test for view partials in Redis I find they are not being cached e.g. In dev environment on the same machine the view partials appear in the redis cache whereas in test they don't - only cached objects appear; this is confirmed by viewing through the redis-cli for both dev and test redis instances. 'render_template' and 'have_content' together with viewing the tested page (Using the 'capybara-screenshot' gem) confirm the content is being served successfully but the partials are not being cached in test.

Gems used specifically in test : rspec-rails, factory_girl_rails, faker, capybara, capybara-screenshot, capybara-user_agent, pry, guard-rspec, launchy, database_cleaner, shoulda-matchers, redis, turn.

I've checked in the spec.rb's that perform_caching is still true; tried temporarily removing pry, guard-rspec, launchy, shoulda-matchers gems but no difference. Tried removing database_cleaner gem, disabling all test cache clearing and ran tests again to find only object caching present in redis, no partials.

test.rb

SmashingSuperApp::Application.configure do

  config.cache_classes = true

  config.whiny_nils = true

  config.consider_all_requests_local       = false

  config.action_dispatch.show_exceptions = true

  config.action_controller.allow_forgery_protection    = false

  config.action_mailer.delivery_method = :test

  config.active_support.deprecation = :stderr

  config.action_controller.perform_caching = true 

  config.cache_store = :redis_store, "redis://localhost:6378/0/cache", { expires_in: 1176.hours }

  ENV["REDIS_URL"] ||= "redis://localhost:6378/0"

  config.action_mailer.raise_delivery_errors = false

  config.active_support.deprecation = :log

  config.action_dispatch.best_standards_support = :builtin

  config.active_record.mass_assignment_sanitizer = :strict

  config.active_record.auto_explain_threshold_in_seconds = 0.5

  config.log_tags = [:uuid, :remote_ip]

  config.before_initialize do |app|
    app.config.paths.add 'app/models', :eager_load => true
  end

  config.to_prepare do
    Dir["#{Rails.root}/app/models/*"].each do |model_name|
      require_dependency model_name unless model_name == "." || model_name == ".."
    end
  end

  Rails.application.routes.default_url_options[:host]= 'smashingsuperapp.co.uk:3000'

end

rails_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'

require 'capybara/rails'
require 'capybara-screenshot/rspec'
require 'shoulda/matchers'
require 'faker'
require 'redis'

RSpec.configure do |config|

  config.include Rails.application.routes.url_helpers

  config.infer_spec_type_from_file_location!

  config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
    Rails.cache.clear # Clear redis cache
  end

  config.before(:each) do |example|
    DatabaseCleaner.strategy= example.metadata[:js] ? :truncation : :transaction
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
    Rails.cache.clear # Clear redis cache
  end

  config.include FactoryGirl::Syntax::Methods

  config.after do |example|
    if example.metadata[:type] == :feature and example.exception.present?
      save_and_open_page
    end
  end

end

def set_host (host)
  default_url_options[:host] = host
  Capybara.app_host = "http://" + host
end 

spec_helper.rb

require 'capybara/user_agent'

Capybara::UserAgent.add_user_agents(mechanize: 'Mechanize')

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.include Capybara::UserAgent::DSL

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

=begin
  config.filter_run :focus
  config.run_all_when_everything_filtered = true

  config.disable_monkey_patching!

  if config.files_to_run.one?
    config.default_formatter = 'doc'
  end

  config.profile_examples = 10

  config.order = :random

  Kernel.srand config.seed
=end
end

def get_path(path)
  parsed_params = Rails.application.routes.recognize_path path
  controller = parsed_params.delete(:controller)
  action = parsed_params.delete(:action)
  get(action, parsed_params)
end

Feature specs use 'visit' and controller specs use 'get' to the correct URL's and render the correct content.

Any pointers as to why partials wouldn't be being cached in this situation very much appreciated. Thanks in advance.

1

1 Answers

0
votes

Thought I'd try adding the 'selenium-webdriver' gem and after lots of irony debugging/trial/error in the test environment I then found cached partials were appearing in Redis. In case it helps others as I found a information a bit patchy;

Further added

gem 'selenium-webdriver'

into test group of Gem file.

Modified /spec/rails_helper.rb below. Note particularly Capybara.server_port = 10000, Capybara.always_include_port = true, Capybara.javascript_driver = :selenium - default port was not being picked up in js marked tests so had to lock it down there.

ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'

require 'capybara/rails'
require 'capybara-screenshot/rspec'
require "rack_session_access/capybara"
require 'shoulda/matchers'
require 'faker'
require 'redis'

require 'support/wait_for_ajax'

RSpec.configure do |config|

  config.include Rails.application.routes.url_helpers

  config.infer_spec_type_from_file_location!

  config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
    Rails.cache.clear # Clear redis cache

    # To prevent FactoryGirl creating persons with an id that are reserved for 'special' persons
    ActiveRecord::Base.connection.execute("ALTER SEQUENCE persons_id_seq START with 4000 RESTART;")
  end

  config.before(:each) do |example|
    DatabaseCleaner.strategy= example.metadata[:js] ? :truncation : :transaction
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
    Rails.cache.clear # Clear redis cache
  end

  config.include FactoryGirl::Syntax::Methods

  config.after do |example|
    if example.metadata[:type] == :feature and example.exception.present?
      save_and_open_page
    end
  end

end

Capybara.server_port = 10000

Capybara.always_include_port = true

Capybara.javascript_driver = :selenium

def set_host (host)
  # host! host
  default_url_options[:host] = host
  Capybara.app_host = "http://" + host
end

The "require 'support/wait_for_ajax'" in the above, described here, is something I came across whilst trying to get selenium up and running that looks handy for ajax testing. Although ajax was not playing a role in the cached partials I was testing for this, there are others with ajax calls where this would be handy.

For the feature tests using selenium, have those tests in their own describe block e.g.

  describe "special person accesses event with JS tests" do

    before(:each) do
      Capybara.current_driver = :selenium
    end

    after(:all) do
      Capybara.use_default_driver
    end

    scenario 'can view persons partial with cache insertion', js: true do

      visit some_cached_page_path(id:some_page_id)

    ...

And then any following tests that use rack_test (default no full browser and no js tests) put them in a separate describe block but without 'js: true' and those before and after blocks. Initially I thought I would only need to mark selenium driven test blocks with "js: true" but found issues then with the basic rack_test based blocks until I took the above describe block approach.