API integration

AngularJS, Pact, and Jasmine

  1. Follow the steps in pact-consumer-js-dsl (yes, with the gem ‘pact-mock_service’)
  2. Here is where the important stuff comes with AngularJS:

given service:

angular.module('ng-cherie.services.current-theme', [
    'ng-cherie.constants'
])
    .factory('currentThemeService', function currentThemeService($http, API) {
        var service = {
            getCurrentTheme: getCurrentTheme
        };

        function getCurrentTheme() {
            var theme;
            return $http.get(API.BASE_PATH + API.CURRENT_THEME)
                .then(function getCurrentThemeSuccess(response) {
                    theme = response.data;

                    return theme;
                }, function() {
                    return null;
                });
        }

        return service;
    });

then pact with ngMidwayTester

/* globals ngMidwayTester */ 
/* globals Pact */
describe('currentThemeService', function() {
    var API,
        currentThemeProvider,
        currentThemeService,
        mockCurrentTheme,
        $httpBackend,
        tester;

    beforeEach(function() {
        angular.module('currentThemeServiceSpec', [
          'ng-cherie.services.current-theme',
          'ng-cherie.constants',
          'ng-cherie.mocks.current-theme',
          'ngMockE2E'
        ]);

        tester = ngMidwayTester('currentThemeServiceSpec');
        currentThemeService = tester.inject('currentThemeService');
        API = tester.inject('API');
        mockCurrentTheme = tester.inject('mockCurrentTheme');

        $httpBackend = tester.inject('$httpBackend');
        $httpBackend.whenGET(/\/api\/v1\/currenttheme/).passThrough();

        currentThemeProvider = Pact.mockService({
            consumer: 'ng-cherie.services.current-theme',
            provider: 'CherieApp',
            port: 1234,
            done: function (error) {
                expect(error).toBe(null);
            }
        });
    });

    afterEach(function () {
        tester.destroy();
        tester = null;
    });

    describe('getCurrentTheme', function() {
       it('should return the current theme via pact', function(done) {

           API.BASE_PATH = "http://localhost:1234" + API.BASE_PATH;

           currentThemeProvider
               .uponReceiving("a request for the current theme")
               .withRequest("get", "/api/v1/currenttheme")
               .willRespondWith(200, { "Content-Type": "application/json" }, mockCurrentTheme);

           currentThemeProvider.run(done, function(runComplete) {
               currentThemeService.getCurrentTheme().then(function(response) {
                   expect(response).toEqual(mockCurrentTheme);
                   runComplete();
               });
           });
       });
    });

});

line 12: the key is to set up a new module that contains ngMockE2E so we can use `passthrough`
line 45: make sure to replace the domain with the pact server location, in this case, it is localhost:1234.

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 (;

GET a list of countries & more

GET

  • /countries

EX RESPONSE:

  • {“countryList”:[
  •       {“countryName”:”United States”,”countryCode”:”US”,”isoCode”:”840″},
  •       {“countryName”:”Canada”,”countryCode”:”CA”,”isoCode”:”124″}]
  • }

TASK:

  • retrieve the list of countries and format it, display countryName in specified language, etc.

——————————————————————————————————————-

module Project

  class ListCountries

    class << self       
      def current_list         
        begin           
          list_countries_url = APP_CONFIG['list_countries_url']           
          list_countries_headers = {               
              'Content-Type' => 'application/json',
              'Accept' => 'application/json',
              APP_CONFIG['api_key_header'] => APP_CONFIG['api_key']
          }
          response, body = APIClient.common_services_get(list_countries_url, list_countries_headers)
          ## APIClient makes HTTP calls with SSL set up (if uri.scheme == 'https') and headers and key and all that good stuff... ##
          if body
            JSON.parse(body)["countryList"]
          end
        rescue => e
          Rails.logger.debug "Project::ListCountries.current_list rescue => #{e.message}"
          raise
        end
      end
      #returns the list of countries
      # => [{"countryName"=>"United States", "countryCode"=>"US", "isoCode"=>"840"},
      #     {"countryName"=>"Canada", "countryCode"=>"CA", "isoCode"=>"124"}]

      def country_names
        current_list.collect{|x| x["countryName"]} unless current_list.nil?
      end
      #returns an array of country names
      # => ["United States", "Canada"]

      def alpha_country_codes
        current_list.collect{|x| x["countryCode"]} unless current_list.nil?
      end
      #returns an array of alpha country names
      # => ["US", "CA"]

      def iso_country_codes
        current_list.collect{|x| x["isoCode"]} unless current_list.nil?
      end
      #returns an array of iso country code
      # => ["840", "124"]

      def translated_country
        begin
          I18n.t(alpha_country_codes, :scope => "countries", :raise => true)
        rescue => e
          Rails.logger.debug "Project::ListCountries.translated_country rescue => #{e.message}"
          raise
        end
      end
      # returns translated countries
      # => ["United States", "Canada"] from en.yml
      # => ["États-Unis", "Canada"] from fr-CA.yml

      def translated_names_to_alpha
        unless current_list.nil?
          names_to_alpha = translated_country.each_with_index.collect{|country, index| {country => alpha_country_codes[index]}}
          names_to_alpha.inject{|k,v| k.merge v}
        end
      end
      # returns a Hash of translated country names to their alpha country codes
      # => {"United States" => "US", "Canada" => "CA"}
    end
  end
end

sample RSPEC:

require 'spec_helper'

describe "ListCountries" do

  context "successful response" do

    before(:each) do
      list_countries = [OpenStruct.new(:code => "200"), {"countryList" =>
                         [{"countryName" => "United States", "countryCode" => "US", "isoCode" => "840"},
                          {"countryName" => "Canada", "countryCode" => "CA", "isoCode" => "124"}]}.to_json]
      APIClient.stub(:common_services_get).and_return(list_countries)
    end


    describe "current_list" do
      it "should return an array of a list of countries" do
        result = Project::ListCountries.current_list
        result.should be_kind_of Array
      end
    end

    describe "country_names" do
      it "should return an array of country names" do
        result = Project::ListCountries.country_names
        result.should be_kind_of Array
        result.should include("United States")
      end
    end

    describe "translated_country" do
      it "should return an array of translated country names" do
        Project::ListCountries.stub(:alpha_country_codes).and_return(%w{US CA})
        Project::ListCountries.translated_country.should == %w{United\ States Canada}
      end

      it "should raise an exception when the country is not translated" do
        Project::ListCountries.stub(:alpha_country_codes).and_return(%w{SG LA})
        lambda { Project::ListCountries.translated_country }.should raise_error
      end
    end

    describe "translated_names_to_alpha_codes" do
      it "should return a hash of translated country names to their alpha country codes" do
        Project::ListCountries.stub(:alpha_country_codes).and_return(%w{US CA})
        Project::ListCountries.stub(:translated_country).and_return(%w{United\ States Canada})
        result = Project::ListCountries.translated_names_to_alpha
        result.should == {"United States" => "US", "Canada" => "CA"}
        result.should be_kind_of Hash
      end
    end
  end
end