skip to Main Content

I’m working on a project that gets data from an external REST API (from a social network such as Facebook, Twitter or Instagram).

I’m not sure that what I’m doing is right or wrong so I need some guidance. I don’t know how, when people create an app which depends on data from outside (a REST API or crawling data), they do TDD with it.

My question is: I’m trying to do a TDD test on a method that calls an external REST API. Is this right or wrong?

  • If it’s right, how can I test it with RSpec? Is there any guide or source that I can read?
  • If it’s wrong, then how can I check it? if I change the API_VERSION to a later version, how can I know that the logic is still working well, and all required fields are still there?

For example:

I have code like this:

API_VERSION = "v2.5"
FIELD_PAGE_GRAPH = %w(id name picture{url} likes cover is_community_page category link website has_added_app 
  talking_about_count username founded phone mission location is_published description can_post checkins company_overview
  general_info parking hours payment_options access_token
)

FIELD_STREAM_GRAPH = %w(id message story comments.summary(true) likes.summary(true).limit(500) from to link shares created_time
  updated_time type is_published attachments scheduled_publish_time application
)

def self.get_stat_facebook(page_id,access_token=nil)
  graph = Koala::Facebook::API.new(access_token)
  graph.get_objects(page_id.to_s,{:fields => FIELD_PAGE_GRAPH}, {:api_version => API_VERSION})
end

def self.get_feed_facebook(page_id,access_token=nil, options = {})
  options = options.with_indifferent_access
  retry_time = 0
  begin
    graph = Koala::Facebook::API.new(access_token)
    params = {:fields => FIELD_STREAM_GRAPH, :limit => 25}
    params.merge!({:since => options[:_since].to_i}) if options[:_since].present?
    params.merge!({:until => options[:_until].to_i}) if options[:_until].present?
    results = []
    loop do
      graph_response = graph.get_object(page_id.to_s+"/feed", params, {:api_version => API_VERSION})
      break if graph_response.blank?
      results = results+graph_response
      break if options[:_since].blank?
      params[:until] = graph_response.sort_by!{|result| result['created_time']}.first['created_time'].to_time.to_i-1
    end
  rescue Koala::Facebook::ServerError
    sleep 1
    retry_time += 1
    retry if retry_time <= 3
  end
  filter_owner_page(results, page_id)
end

Then I have a spec like

require 'spec_helper'

RSpec.describe SocialNetwork do
  context ".get_stat_facebook" do
    it "when access token is expired"
    it "when access token is not expired"
    it "when page id is not exist"
    it "when page id is exist"
  end

  context ".get_feed_facebook" do
    it "when access token is expired"
    it "when access token is not expired"
    it "when page id is not exist"
    it "when page id is exist"
    it "data contain id field"
    it "data contain message field"
    it "data contain attachment field"
  end
end

4

Answers


  1. It sounds to me that what you want to achieve is more functional/acceptance test instead of unit test. Personally I think in unit testing, you should isolate your unit (method) and try to inject required dependencies (mock) and evaluate the output of your function (expect and assert).

    In your case I think you can expect sdk method that you use, for example, you have following method:

    def do_something_with_facebook
      @graph = Koala::Facebook::API.new(oauth_access_token)
    end
    

    in that case I would write a test that check if your method calls Koala::Facebook::API like following.

    def test_method_calls_koalla
        grape = mock
        Koala::Facebook
          .expects(:new)
          .returns(grape)
    
        method = do_something_with_facebook
      end
    

    It might not be rspec syntax but I hope it gives you some idea.

    Login or Signup to reply.
  2. Yes, it’s appropriate for tests to hit external services, but you can minimize the impact of that on your test suite in a couple of ways.

    I would test this code as follows:

    • Register a Facebook test user.
    • Write RSpec feature specs or Cucumber scenarios to test the entire feature that uses Facebook through the entire stack, using the test user. Use the VCR gem to record Facebook’s responses so that the remote calls don’t slow down your tests.
    • Write RSpec specs (unit tests) for SocialNetwork.

      • In one or a few unit tests per different call to Facebook, let them hit Facebook using the test user and, again, use VCR to keep them fast. Optionally, let one or a few of them hit Facebook all the time so that you know if Facebook’s API changes.
      • In the rest of your specs of SocialNetwork, ones that just test variations of calls that you already tested, stub out Koala.

      It’s hard to explain exactly which tests should hit Facebook and which should use stubs without having them in front of us. See how it goes and post again if you need more advice.

    Login or Signup to reply.
  3. TDD is for testing your methods as a unit. Data coming from outside can be mocked so you can cover each of the scenarios. Something like

    graph = double()
    allow(graph).to receive(:get_object).and_return(data)
    

    I would also change

    context ".get_stat_facebook" do

    for

    describe ".get_stat_facebook" do

    And use context to describe the scenarios you want to test. It will improve readability.

    More: big methods are difficult to test, so you can break your #get_feed_facebook into small parts (like build the params, the loop, etc) to improve your testability.

    Login or Signup to reply.
  4. You say, “I’m trying to do a TDD test on a method that calls an external REST API.” To me, this rings of unit testing (the phrase that pays being ‘test on a method’). “Is this right or wrong?”, you ask. Definitely right (IMO).

    My current project makes extensive use of external APIs from multiple other systems. I use webmock. It gives me a LOT of control in making sure the request is well-formed (url, query, headers, etc.) and let’s me test a wide range of responses (success, permission denied, network timeout, etc.). And, it’s easy to manage external API versions.

    For me, this has been the most straight-forward, low-overhead approach to testing methods that access external APIs. Happy to say more if it’s of interest.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search