Month: February 2013

testing cookies is almost as fun as eating them

Given: we have two different apps A and B
And: we GET data X from backend (Java) API
When: we pass “secret” data X from app A to app B
Then: we use a secure cookie to store data X

#controller

def index
    begin
      data = Data.find(@id)
      data_X = data['X']
      if data['X'] == "Keep"
        cookies[:X] = {
          :value => "true",
          :secure => true,
          :domain => '.www.cheriecodes.com',
          :httponly => true
        }
      elsif data['X'] == "Remove"
        cookies[:X] = {
          :value => "false",
          :secure => true,
          :domain => '.www.cheriecodes.com',
          :httponly => true
        }
      end

    # some other code here that directs to app B #
  
    rescue ActiveResource::ResourceNotFound => e
      Rails.logger.debug "#index: fails to find data"
    rescue => e
      Rails.logger.debug "#index error: #{e.message}"
    end
  end

note: yes, we should take out the cookie into its own method to keep it DRY, but that’s not my topic today d: so ahem… how do we test for cookies?

#rspec

context "when data X is equal to 'Keep'" do
    it "sets the cookie X to true" do
        Data.stub(:find).and_return({'X' => "Keep"})
        get :index
        cookies[:X].should == "true"
    end
end

but it fails miserably…

Failure/Error: cookies[:X].should == "true"
       expected: "true"
            got: nil (using ==)

What? How’s that possible?? Hmm… let’s get a better view of the whole spec

describe "#index" do
    before :each do
        @mocked_cookies = mock('cookie store').as_null_object
        controller.stub(:cookies).and_return(@mocked_cookies)
        @mocked_cookies.stub(:[]).with(:country_preference).and_return(nil)
    end

    # other specs #

    context "when data X is equal to 'Keep'" do
        it "sets the cookie X to true" do
          Data.stub(:find).and_return({'X' => "Keep"})
          get :index
          cookies[:X].should == "true"
        end
    end
end

Ahhhhhhhhhhh… somebody already stubbed the cookies to return nil

so to fix this, separate out the two different specs into its own describe block and move the mocked cookies into the appropriate describe block (not the one that tests for cookies[:X]) and the test passes (:

Side Question: can you think of any other way to pass “secret” (private) information between app A and app B?

Advertisements

rails, getJSON, and jasmine

Task:
When user selects a country from the country dropdown,
get the new terms and conditions links according to the selected country,
and update the current terms and conditions links with the new ones.

controller

def update_policy_links
    respond_to do |format|
      format.js {
        render :json => {:updated_terms_link => 
                          return_policy_link("terms", country_preference),
                         :updated_privacy_link => 
                          return_policy_link("privacy", country_preference),
                         :updated_electronic_link => 
                          return_policy_link("electronic", country_preference)}
      }
    end
end

country_preference: a cookie that stores the latest country user selected
return_policy_link: check out the previous post on default value & nil guard

routes

get '/update_policy_links/:country' => "enrollment/user#update_policy_links", :constraints => {:country => /[a-zA-Z]{2}/}

note: we’re passing in country as a param for other usage which I won’t go over here…

javascript

$("#new_vuser").on('blur', '#user_countryCode', function(){
    country_selected = $(this).val();

    $.getJSON("/update_policy_links/" + country_selected + "?locale=" + I18n.locale, function(data){
          $(".terms_link, #terms_link").attr('href', data.updated_terms_link);
          $(".privacy_link, #privacy_link").attr('href', data.updated_privacy_link);
          $(".electronic_link").attr('href', data.updated_electronic_link);
    });
});

jasmine

describe("on_select_change", function(){
    it("should update the terms and condition links", function(){

        var links ={
            updated_terms_link : "/pages/terms",
            updated_privacy_link : "/pages/privacy",
            updated_electronic_link : "/pages/electronic"
        };

        spyOn($, 'getJSON').andCallFake(function(url,data){ data(links); });
        $('#user_countryCode').blur();

        expect($.getJSON).wasCalled();
        expect($('.terms_link, #terms_link')).toHaveAttr('href',"/pages/terms");
        expect($('.privacy_link, #privacy_link')).toHaveAttr('href',"/pages/privacy");
        expect($('.electronic_link')).toHaveAttr('href',"/pages/electronic");
    });
})

default argument value & nil guard

suppose we have this following method, which we call up to 3 times for 3 different types of policy.

  def return_policy_link(type, country)
    policy_urls ||= Cherie::TermsAndConditions.get_policy_urls(country.upcase)
    custom_url(policy_urls[type])
  end

But what if country is “dynamic”?
As in: return_policy_link(“terms”, cookies[:country_preference])
cookies[:country_preference] is updated according to the user’s selection, browser, etc…

anyway, point is, we don’t know what country could be…

Let’s update our country argument to default to “US”, just in case country is missing:

  def return_policy_link(type, country="US")
    policy_urls ||= Cherie::TermsAndConditions.get_policy_urls(country.upcase)
    custom_url(policy_urls[type])
  end

kay, is that good enough?

Let’s run our rspec!

    describe "#return_policy_link" do
      before :each do
        us_links = {"electronicDisclosure" => "pages/electronic_communication", "terms" => "pages/terms", "privacy" => "pages/privacy"}
        Cherie::TermsAndConditions.stub(:get_policy_urls).with("US").and_return(us_links)
      end

      context "get terms policy link for US" do
        it "returns the right url" do
          controller.return_policy_link("terms", "US").should include("pages/terms")
        end
      end
      context "get terms policy link for missing country" do
        it "returns the terms link for US" do
          controller.return_policy_link("terms").should include("pages/terms")
        end
      end
    end

so far so good, but what if country is nil?

   context "get terms policy link when country is nil" do
     it "returns the terms link for US" do
       controller.return_policy_link("terms", nil).should include("pages/terms")
     end
   end

rspec fails ):

we need a nil guard and still default to “US”

  def return_policy_link(type, country = "US")
    country ||= "US"
    policy_urls ||= Cherie::TermsAndConditions.get_policy_urls(country.upcase)
    custom_url(policy_urls[type])
  end

alrighty, now all the tests pass (:

Hmm… can we refactor rspec?

    describe "#return_policy_link" do
      let(:terms_link) {{"terms" => "pages/terms"}}

      before :each do
        Cherie::TermsAndConditions.stub(:get_policy_urls).and_return(terms_link)
      end

      context "returns the expected terms link with type as terms and country" do
        it "is US" do
          controller.return_policy_link("terms", "US").should include(terms_link["terms"])
        end
        it "is missing" do
          controller.return_policy_link("terms").should include(terms_link["terms"])
        end
        it "is nil" do
          controller.return_policy_link("terms", nil).should include(terms_link["terms"])
        end
      end

not sure if I am really digging the new description, even tho it shows up nicely as:
#return_policy_link returns the expected terms link when type is terms and country is US
what do you think?

Jasmine – how to mock window.location

I got some javascript code that I wanna test using Jasmine:

Cherie.Controllers.Help = function () {
    var $question = $('.question'),
        $help_section = $('#help_section');

    var event_handlers = {
        hide_event:{
            highlight_questions:function () {
                if (window.location.pathname == "/help") {
                    $question.each(function () {
                        if ($(this).attr('class').indexOf('highlight') == -1)
                            $(this).hide();
                    })
                }

            }
        },

        click_event:{
            question_list:function () {
                $help_section.on('click', '.question', function () {
                    $(this).next('.answer').toggle();
                    $this.find('.toggle').toggleClass('hide');
                });
            }
        }
    }

    $.each(event_handlers, function(i,handler_type){ $.each(handler_type, function(i,handler){ handler(); }) });
}

$(function() {
    Cherie.Controllers.Help();

});

I am gonna start with testing the hide_event, so I got to mock window.location.pathname… window.location can be written without the window prefix, and location is an object, which makes pathname a property (location.property), so we can’t “spyon” it, since spyon is used for mocking/stubbing functions, so let’s work around it so that we can use spyOn.

make a helper function:

var get_current_location = {
    get_pathname: function () {
        return window.location.pathname;
    }
};

and now the new code looks like this:

        hide_event:{
            highlight_questions:function () {
                if (get_current_location.get_pathname() == "/help") {
                    $question.each(function () {
                        if ($(this).attr('class').indexOf('highlight') == -1)
                            $(this).hide();
                    })
                }

            }
        }

great, on to jasmine…

describe("help.js", function () {
    beforeEach(function () {
        loadFixtures('help.html');
    });

    describe("hide_event", function (){
        it("should hide non-highlighted questions", function () {
            spyOn(get_current_location, 'get_pathname').andReturn("/help");
            Cherie.Controllers.Help();
            expect($('.highlight')).toBeVisible();
            expect($('.question:not(.highlight)')).toBeHidden();
        });

    });

    describe("click_event", function(){
        beforeEach(function(){
            Cherie.Controllers.Help();
            $('.question').first().click();
        });

        it("should show the corresponding answer", function(){
            expect($('.answer').first()).toBeVisible();
        });

        it("should show a '-' image with .hide", function(){
            expect($('.toggle').first()).toHaveClass('hide');
        });

        it("should show a '+' image without .hide on 2nd click", function(){
            $('.question').first().click();
            expect($('.toggle').first()).not.toHaveClass('hide');
        })
    });


});

& it’s alllll good (:

sidenote: The reason we have it as javascript is because we use the same partial across several different pages (not all questions are hidden); another way to implement it w/o javascript is to use conditionals along with css such as .hidden{display:none;}

hide_question = request.path == "/help" ? "hidden" : "" 

and then add the following to certain question list items.

{:class => "#{hide_question}"} 

There are a couple different ways to do this but you get the gist (; which one do you like better? javascript or rails way?