API Testing 101's: Quick and Easy






Anisha Narang
Senior Quality Engineer
Red Hat Inc.
@anisha_narang

DevConf.CZ, 2020

Agenda


  • Overview of REST API
  • Introduction to API testing
  • Overview of different API testing frameworks
  • Introduction to LCC framework(lemoncheesecake)
  • Understanding the matchers/assertions available
  • Generating awesome reports
  • Demo

What is REST API?

  • API stands for Application Programming Interface. API enables communication and data exchange between two separate software systems.
  • REST is an architectural style, or design pattern, for APIs.
  • REST stands for REpresentational State Transfer. It means when a RESTful API is called, the server will transfer to the client a representation of the state of the requested resource.
  • The representation of the state can be in a JSON or XML format
  • API endpoint:
    An identifier for the resource you are interested in. This is the URL for the resource, also known as the endpoint.
  • HTTP method:
    The operation you want the server to perform on that resource, in the form of an HTTP method.
    GET, POST, PUT and DELETE

What is API Testing?


  • API Testing is a software testing type that validates the API(Application Programming Interface) of any application.
  • In short: you send calls to the API and verify the API response.

HTTP methods:

GET

The GET method is used to retrieve information from the given server using a given URI. Provides READ only access to a resource.

POST

The POST method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server.

PUT

Replaces all current representations of the target resource with the uploaded content.

DELETE

The DELETE method deletes the specified resource.

Different API testing frameworks

  • Curl: curl provides a generic, language-agnostic way to demonstrate HTTP requests and responses.
  • Postman: Postman is a Google Chrome app for interacting with HTTP APIs. It offers a friendly GUI for constructing requests and reading responses.
  • JMeter: JMeter (open source) is widely used for functional API testing although it was actually created for load testing.
  • Karate: Karate works out of the box and allows you to construct the most complex request-response operations with no knowledge of any programming language.
  • Pytest: Pytest is a testing framework which allows us to write test codes using python.
  • but there is more with Python..
  • Let's try out some 'lemoncheesecake' ;)

lemoncheesecake: a functional test framework for Python

Getting started


lemoncheesecake can be installed via pip


$ pip install lemoncheesecake

Writing your test suite

Creating a new test project

$lcc bootstrap test-project

Creates a new project directory “test-project” containing one file “project.py” (it contains your project settings) and “suites” and "fixtures" directories for you add your test suites and fixtures.

A suite looks like:


#suites/test_get_users.py

import lemoncheesecake.api as lcc
from lemoncheesecake.matching import *
import requests


SUITE = {
    "description": "Sample API tests for GET, POST, DELETE "
}

base_url = "https://jsonplaceholder.typicode.com"

@lcc.test("Verify that GET request for users/1 gives a 200 OK")
def test_get_users():
    lcc.set_step("checking for endpoint: https://jsonplaceholder.typicode.com/users/1")
    response = requests.get(url=base_url + "/users/1")
    check_that("response code", response.status_code, equal_to(200), quiet=False)

@lcc.test("Verify that GET request for /users/1 contains required params")
def test_get_users_params():
    response = requests.get(url=base_url + "/users/1")
    result = response.json()
    check_that("the response ", result, has_entry("name"))
    require_that("the response", result, has_entry("email", contains_string("@")))
    assert_that("the response ", result, has_entry("username", equal_to("Bret")))
               

Running the test suite:

$lcc run

Console output look like:

Matchers

The matchers

  • equal_to(expected)
  • not_equal_to(expected)
  • is_true()
  • is_false()
  • is_dict([expected])
  • is_list([expected])
  • has_item(expected)
  • has_items(expected)
  • has_entry(expected_key [,expected_value])
  • is_(expected)
  • is_not(expected)
  • and there are more...

The matching operations

  • check_that(hint, actual, matcher, quiet=False)
    run the matcher, log the result and return the matching result as a boolean
  • require_that(hint, actual, matcher, quiet=False)
    run the matcher, log the result and raise an AbortTest exception in case of a match failure
  • assert_that(hint, actual, matcher, quiet=False)
    run the match, in case of a match failure (and only in this case) log the result and raise an AbortTest exception
data = {"title": "Title 1", "uuid": "aaa11"}
check_that("title is", data, has_entry("title", equal_to("Title 1")))
check_that("uuid is", data, has_entry("uuid", equal_to("aaa11")))
                

Sample JSON Output:


{
  "id": 1,
  "name": "Leanne Graham",
  "username": "Bret",
  "email": "Sincere@april.biz",
  "address": {
    "street": "Kulas Light",
    "suite": "Apt. 556",
    "city": "Gwenborough",
    "zipcode": "92998-3874"
  },
  "phone": "1-770-736-8031 x56442",
  "website": "hildegard.org"
  }
}
                

Let's use the matchers and write more tests..


@lcc.test("Verify that GET request for /users/1 contains required params")
def test_get_users_params():
    response = requests.get(url=base_url + "/users/1")
    result = response.json()
    check_that("the response ", result, has_entry("name"))
    require_that("the response", result, has_entry("email", contains_string("@")))
    assert_that("the response ", result, has_entry("username", equal_to("Bret")))
    check_that("the response ", result, has_entry("address", is_dict()))
    require_that("the response", result, has_entry("address", has_entry("city", equal_to("Gwenborough"))))


@lcc.test("Verify that POST request to /posts is successful")
def test_post_req():
    body = {
      "title": 'Title for Devconf.CZ 2020',
      "body": 'Test title by Anisha',
      "userId": 500
    }
    response = requests.post(base_url + "/posts", data=body)
    result= response.json()
    require_that("the POST request response", response.status_code, equal_to(201))
    lcc.log_info(str(result))
    check_that("the response", result, has_entry("title", equal_to("Title for Devconf.CZ 2020")))
                

Logging


  • lemoncheesecake provides logging functions that give the user the ability to log information beyond the checks.

  • log_debug(msg)
  • log_info(msg)
  • log_warn(msg)
  • log_error(msg)

user_name = "User 1"
lcc.log_info("User name to be added: %s " % user_name)

Generating (awesome) reports

Any questions?


Thank You :-)


@anisha_narang

Slides available at: https://anarang.github.io/api101s_devconf2020