6

I am unable to successfully test custom HTTP variables in my Rails 4.2 application using minitest—for some reason, Rails doesn't seem to recognize them at all.

I have the following test:

require "test_helper"

describe ApplicationController do
  tests HomeController

  before do
    cookies[:locale] = "ru"
    request.headers["Accept-Language"] = "zh-cn, en-us"
    request.headers["CloudFront-Viewer-Country"] = "FR"
    I18n.default_locale = :en
  end

  it "sets locale from URL params first" do
    get :index, locale: "de"
    I18n.locale.must_equal :de
  end

  it "sets locale from a cookie second" do
    get :index
    I18n.locale.must_equal :ru
  end

  it "sets locale from Accept-Language header third" do
    cookies.delete :locale
    get :index
    I18n.locale.must_equal :"zh-CN"
  end

  it "sets locale from CloudFront-Viewer-Country header last" do
    cookies.delete :locale
    request.headers["Accept-Language"] = nil
    get :index
    I18n.locale.must_equal :fr
  end
end

And the corresponding controller:

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  before_filter :set_locale

  def default_url_options(options = {})
    { locale: I18n.locale }.merge options
  end

  private

  def set_locale
    I18n.locale = params[:locale] ||
                  cookies[:locale] ||
                  extract_locale_from_accept_language_header ||
                  set_locale_from_cloudfront_viewer_country ||
                  I18n.default_locale

    unless request.fullpath =~ /^\/(?:[a-z]{2,2})(?:[-|_](?:[A-Z]{2,2}))?/
      redirect_to %(#{request.protocol}#{request.host}/#{I18n.locale}#{request.fullpath unless request.fullpath == "/"}), status: :found
    end
  end

  def extract_locale_from_accept_language_header
    if request.env["Accept-Language"]
      request.env["Accept-Language"].scan(/(?:[a-z]{2,2})(?:[-|_](?:[A-Z]{2,2}))?/i).first
    end
  end

  def set_locale_from_cloudfront_viewer_country
    case request.env["CloudFront-Viewer-Country"]
    when "FR"
      return :fr
    else
      return I18n.default_locale
    end
  end
end

The last two test fail because Rails doesn't seem to recognize the HTTP header variables that I set and everything falls through to the default locale:

Failure:
ApplicationController#test_0004_sets locale from CloudFront-Viewer-Country header last [/Users/aporter/Developer/com/wavetronix/www/test/controllers/application_controller_test.rb:33]
Minitest::Assertion: Expected: :fr
  Actual: :en

Failure:
ApplicationController#test_0003_sets locale from Accept-Language header third [/Users/aporter/Developer/com/wavetronix/www/test/controllers/application_controller_test.rb:26]
Minitest::Assertion: Expected: :"zh-CN"
  Actual: :en

If I change Accept-Language to HTTP_ACCEPT_LANGUAGE, that test then passes.

According to the Rails documentation, my tests should work. I Googled this problem several different ways looking for an answer and came up empty. There are a few questions similar to this one on Stack Overflow, but none of the answers or suggestions for those questions solve my problem.

What am I missing?

Environment:

  • Mac OS X 10.11.1
  • Ruby 2.2.2
  • Rails 4.2.4
  • minitest 5.8.0
partydrone
  • 431
  • 3
  • 14

2 Answers2

9

Rails sets a request.env variable for custom HTTP headers by:

  • converting the header to all uppercase
  • replacing dashes with underscores
  • prepending "HTTP_"

So you can set request.headers["Accept-Language"] in the test, and access that value with request.env["HTTP_ACCEPT_LANGUAGE"] in the controller. Likewise, you set request.headers["CloudFront-Viewer-Country"] in the test, and access that value with request.env["HTTP_CLOUDFRONT_VIEWER_COUNTRY"] in the controller.

partydrone
  • 431
  • 3
  • 14
1

The Rails testing guide tell us about Setting Headers:

"HTTP headers and CGI variables can be set directly on the @request instance variable:..."

So you need to write it like this:

@request.headers["Accept-Language"] = "zh-cn, en-us"

I think it is better when you not set up the headers in the before method, but directly in the test. So each time you set only the header you need for this test. And you don't need set the cookie first, and then delete it.

Meier
  • 3,773
  • 1
  • 14
  • 43
  • 1
    That's the same documentation I reference in my post, which isn't working (with or without the `@`). I'm not just testing each method for setting the locale individually, I want to test the _order_ in which they happen—like a fallback chain (i.e., URL parameter always takes precedence. If that's not available, then the cookie always takes precedence. If neither of those are available, then Accept-Language header takes precedence, etc.). – partydrone Sep 29 '15 at 17:07