RSpec

activeresource model rspec

Given: certain API to obtain merchant information: “/service/merchant/:merchantId”
When: it is called with the merchant id,
Then: it returns a json response like the following:

{"store": {
  "preferences" : [{
    "settings" : [
      {
        "key": "logo",
        "value": "logo.png"
      },
      {
        "key": "text",
        "value": "Hi, welcome to our store"
      },
      {
        "key": "url",
        "value": "http://somestore.com"
      }]
    }]
}}

Then: turn the store settings into a hash like the following:

{:logo => "logo.png", :text =>"Hi, welcome to our store", :url => "http://somestore.com"}

Assuming that we already have a method called get_merchant_info that performs the above task – turning the json response into a hash containing the information.

rspec

describe "#get_merchant_info" do
  context "when the service returns a 200 - data found" do
    it "returns a Hash containing the merchant's information" do
      api_data = {:logo => "logo.png", :text =>"Hi, welcome to our store", :url => "http://somestore.com"}
      
      # settings = mock('settings', <TODO: hash that resembles the json response>  )
      store = mock('store', {:preferences =>[settings]})
      MerchantService.any_instance.should_receive(:store).at_least(1).times.and_return(store)

      merchant = MerchantService.new
      merchant.get_merchant_info.should be_kind_of(Hash)
      merchant.get_merchant_info.should eq(api_data)
    end
  end
end

note: MerchantService is the model that integrates with the API and receives the json response.

The reason I set up api_data at the beginning is so I can reuse it for both json response and the hash result.

Now how do we turn api_data into a hash that resembles the json response (for line 6 above)?

After taking a look at Enumerable – http://apidock.com/ruby/Enumerable, I decided to use collect

So I test it out via the console/irb:

api_data.collect{|k,v| {"key" => k.to_s, "value" => v}}
 => [{"key"=>"logo", "value"=>"logo.png"}, {"key"=>"text", "value"=>"Hi, welcome to our store"}, {"key"=>"url", "value"=>"http://somesite.com"}] 

yayyy! Just what we’re looking for.

But is our test efficient? Personally, I don’t like how we’re mocking twice and stubbing the response, why don’t we set up the response like so…

describe "#get_merchant_info" do
  context "when the service returns a 200 - data found" do
    it "returns a Hash containing the merchant's information" do
      api_data = {:logo => "logo.png", :text =>"Hi, welcome to our store", :url => "http://somestore.com"}
      merchant = MerchantService.new({:store =>
                                          {:preferences => [{
                                                :settings =>
                                                    api_data.collect { |k, v| {"key" => k.to_s, "value" => v} }
                                           }]} })
      merchant.get_merchant_info.should be_kind_of(Hash)
      merchant.get_merchant_info.should eq(api_data)
    end
  end
end

which way do you think is better? hmm? or maybe you can think of a way that’s even better? let me know (;

Advertisements

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?

Route Constraints

TASK:

  • constraint T&C policy routes according to its parameters.

EX:

  • /en/us/legal/terms.f0a6f2aa-8e68-45ae-a875-84444ef1130f

constraints:

  • languageCode – 2 alpha characters – EX: en/ fr/ sp
  • country – 2 alpha characters – EX: us/ ca/ uk
  • policy – 3 types – EX: Terms/ Privacy/ Electronic
  • guid – standard – 32 hexadecimal digits – EX: 21EC2020-3AEA-1069-A2DD-08002B30309D

—————————————————————————————————————–
rspec:
note: the guid is located after the “.”, which works as a separator for formatted routes,
hence the :format=>guid instead of :guid => guid.

describe "GET policies" do
    it "should route to us privacy page" do
      { :get => '/en/us/legal/privacy.f0a6f2aa-8e68-45ae-a875-84444ef1130f'}.should
      route_to(:controller=>"pages", :action=>"privacy")
    end

    it "should route to ca terms page" do
      { :get => '/en/ca/legal/terms.f0a6f2aa-8e68-45ae-a875-84444ef1130f'}.should
      route_to(:controller=>"pages", :action=>"terms_ca")
    end

    it "should route to 404 page when given invalid policy parameter" do
      { :get => '/en/ca/legal/invalid.f0a6f2aa-8e68-45ae-a875-84444ef1130f'}.should
      route_to(:controller=> "errors", :action => "routing",
               :path=>"en/ca/legal/invalid",
               :format=> "f0a6f2aa-8e68-45ae-a875-84444ef1130f")
    end

    it "should route to 404 page when given invalid country parameter" do
      { :get => '/en/invalid/legal/terms.f0a6f2aa-8e68-45ae-a875-84444ef1130f'}.should
      route_to(:controller=> "errors", :action => "routing",
               :path=>"en/invalid/legal/terms",
               :format=> "f0a6f2aa-8e68-45ae-a875-84444ef1130f")
    end

    it "should route to 404 page when given invalid language parameter" do
      { :get => '/invalid/us/legal/privacy.f0a6f2aa-8e68-45ae-a875-84444ef1130f'}.should
      route_to(:controller=> "errors", :action => "routing",
               :path=>"invalid/ca/legal/privacy",
               :format=> "f0a6f2aa-8e68-45ae-a875-84444ef1130f")
    end

    it "should route to 404 page when given invalid guid" do
      { :get => '/en/us/legal/privacy.invalid'}.should
      route_to(:controller=> "errors", :action => "routing",
               :path=>"en/ca/legal/privacy",
               :format=> "invalid")
    end

  end

# routes.rb

constraints(PolicyConstraint) do
   get ':language/:country/legal/:policy.:guid' => "pages#policy_by_type",
        :constraints => {:language => /[a-zA-Z]{2}/, :country => /[a-zA-Z]{2}/,
        :guid=>/[\h]{8}-[\h]{4}-[\h]{4}-[\h]{4}-[\h]{12}/}

end

An example of policy_by_type method that is used for filtering two countries: CA and US.
# pages_controller.rb

  def policy_by_type
    if params[:country].upcase == "CA"
      case params[:policy]
        when "terms"
          redirect_to terms_ca_url
        when "privacy"
          redirect_to privacy_ca_url
        when "electronic_communication"
          redirect_to electronic_communication_ca_url
        else
          render_404
      end
    else
      case params[:policy]
        when "terms"
          redirect_to terms_url
        when "privacy"
          redirect_to privacy_url
        when "electronic_communication"
          redirect_to electronic_communication_url
        else
          render_404
      end
    end
  end

now for constraining policy segment:
# constraints/policy_constraint.rb

class PolicyConstraint
   def self.matches?(request)
     policy_types = ['terms', 'privacy', 'electronic']
     policy_types.include?(request.parameters[:policy])
   end
end

and viola the route is now “constrained” (:

more info: http://guides.rubyonrails.org/routing.html