I was knocked out! I was asked a question about API testing and I was mentally knocked out. While I have tested APIs before, I have been focused on front-end testing lately. The last time I did it in an automated fashion was as a proof of concept. At that moment, I realized I have been focusing on front-end test automation too much and I really needed to brush up on my back-end testing skills.
Last weekend, I challenged myself to learn API testing two ways in two languages. I decided to do this in Ruby and Python. I am most comfortable with Ruby, so I wanted to be sure I knew an approach. As for Python, I have never worked with it before this challenge. As it is a popular language for programming in general and a common one for test automation, I decided it was a must try. I found it to be a straightforward language to learn and am looking forward to doing more work with it.
Why API Testing?
API Testing is the best way to test back-end endpoints and integrations. Having a solid API Testing framework in place can reduce some front-end testing needs and creates a solid cross platform testing strategy with mobile applications. In the challenge that I took, I focused on getting information for the API.
The Approach
I decided to go with functional test driven and behavioral driven test patterns for this challenge. In order to do this, I needed to find an API to test against. After a quick Google search, I found a list of APIs that are free for testing on GitHub (found here) and decided that I would work with the iTunes API for queries.
Functional Testing: Ruby
Starting with Ruby was the natural choice, as it was already installed on my computer and I wanted to get my feet wet. I went with Capybara as my driver in this exercise. Capybara is an awesome headless browser that enables fast testing. I have truly been enjoying it lately. Below is my code.
require 'json' require 'rspec' require 'ci/reporter/rake/test_unit_loader' gem 'test-unit' require 'test/unit/ui/console/testrunner' require 'capybara' require 'capybara/dsl' require 'capybara/rspec' require 'capybara/poltergeist' require 'rest-client' class API < Test::Unit::TestCase include Capybara::DSL def setup Capybara.register_driver(:poltergeist) { |app| Capybara::Poltergeist::Driver.new(app, js_errors: false, timeout: 300, :phantomjs_options => ['--ssl-protocol=tlsv2']) } # , timeout: 300 Capybara.default_driver = :poltergeist # configure Capybara to use poltergeist as the driver $driver = Capybara.current_session end def teardown end def test_itunes response = RestClient.get 'https://itunes.apple.com/search?term=the+killers&entity=musicVideo' assert_equal response.code,200 data_hash = JSON.parse(response) result_count = data_hash['resultCount'] assert_equal result_count, 50 end end
This is a straightforward approach that is to the point and that is why I like it. In this test, I’m only asserting two validation points, : the first is a status code of 200 and the second is that the default result Count is 50. This is not a sophisticated test, but instead a practical starting point.
Behavioral Driven Testing: Ruby
Sticking with Ruby, I switched gears to Behavioral Driven testing with Cucumber. I believe that Behavioral Driven testing is an excellent area of testing because, here you are focused on the user. With regards to API, it might as great a fit as Functional Driven testing, but it’s is one that speaks to Product. In this example I stuck with Capybara and will show you a few of the scenarios and steps that I used.
itunes.feature
Feature: Testing iTunes API Background: Given that I can access the iTunes API Scenario: Search for results of The Killers Then I can search for The Killers And get the Results Count Scenario: Search for Music Videos starting The Killers Then I can search for Hot Fuss by The Killers And see Hot Fuss in the results stream Scenario: Search for the catalog of a band Then I should be able to search for "La Roux" And see artist results for "La Roux"
itunes_steps.rb
Given (/^that I can access the iTunes API$/) do $client = RestClient.get 'https://itunes.apple.com/search' expect($client.code).to eq(200) end Then (/^I can search for The Killers$/) do $client = RestClient.get 'https://itunes.apple.com/search?term=the+killers' expect($client.code).to eq(200) end And (/^get the Results Count$/) do data_hash = JSON.parse($client) result_count = data_hash['resultCount'] expect(result_count).to eq(50) end Then (/^I can search for Hot Fuss by The Killers$/) do $client = RestClient.get 'https://itunes.apple.com/search?term=the+killers+hot+fuss' expect($client.code).to eq(200) end And (/^see Hot Fuss in the results stream$/) do data_hash = JSON.parse($client) result_count = data_hash['resultCount'] results_details = data_hash['results'] expect(result_count).to eq(11) expect(results_details.to_s).to include("Hot Fuss") end Then (/^I should be able to search for "([^"]*)"$/) do |band| band_name = band.gsub(/\s+/, '+') $client = RestClient.get 'https://itunes.apple.com/search?term=' + band_name expect($client.code).to eq(200) end And (/^see artist results for "([^"]*)"$/) do |band| data_hash = JSON.parse($client) results_details = data_hash['results'] expect(results_details.to_s).to include(band) end
In this case, I did it a few different ways, hard coded (bad) and variable coded (good). This is again, more a less a proof of concept and not expected to be perfect. I was looking to experiment with the code, starting first on making it work and second on refining the code.
Functional Testing: Python
Finally, I decided to try my hand at Python. As I mentioned above, I had never worked in Python before this attempt. I found the language to be easy to pick up, it does have a look and feel similar to Ruby, so that made it easier for me.
import json import requests import unittest class TestAPIReponses(unittest.TestCase): def test_validate_status_code(self): response = requests.get("https://itunes.apple.com/search?term=the+killers") self.assertEqual(200, response.status_code) def test_validate_results_count(self): response = requests.get("https://itunes.apple.com/search?term=the+killers") json_data = json.loads(response.content) results = json_data['resultCount'] results_details = json_data['results'] self.assertEqual(50, results) self.assertIn("Hot Fuss", str(results_details)) def test_japanese_music(self): response = requests.get('https://itunes.apple.com/search?term=ayumi+hamasaki&limit=100') json_data = json.loads(response.content) results = json_data['resultCount'] results_details = json_data['results'] self.assertEqual(100, results) self.assertIn("Ayumi Hamasaki", str(results_details)) if __name__ == '__main__': unittest.main()
In this attempt I had three tests with several different assertions. This gave me an introduction into how asserts work in Python and how data is returned.
Conclusion
In this challenge, I wanted to prove to myself at least, that I had not lost my mind and my skills. When I first sat down to work on this challenge, I remember I had created a Module for returning information to the TestRail API in Ruby before. This is not perfect code befitting of a code review, but I wanted to show you my cards and thoughts when working through a challenge or proof of concept. Usually, I start by writing enough code as needed to test a proof of concept. This is usually a base page and a few tests around basic navigation and then I start to build out other classes after the initial tests are working and producing results. I refine the code later.
It is important to include API Testing in any testing strategy and should not be neglected. I’m glad I went forward with this challenged for that reason. I’ll continue to play with API testing in addition to my usual front-end testing efforts.
This challenge took less than a few (highly) interrupted hours. The two biggest problems were identifying an API to test against and then installing and ramping up on Python. I liked what I saw in Python and am looking forward to playing around with it further.
Connect with me
If you liked this post, take a quick minute to sign up.