"""
.. module:: test_class
:synopsis: Testclass and mixing for using together with unittest
.. moduleauthor:: Yauhen Kirylau <actionless.loveless@gmail.com>
"""
import json
from pprint import pprint
from unittest import TestCase
from requests import Session
from .expect import (
expect_status, expect_status_in,
expect_json, expect_json_match, expect_json_contains,
expect_headers, expect_headers_contain,
expect_json_length, expect_body_contains,
)
from .general import apply_path, WooperAssertionError
[docs]class ApiMixin:
"""
This class can be used as a mixin to `unittest.TestCase
<https://docs.python.org/3.4/library/unittest.html#unittest.TestCase>`_
to provide additional methods for requesting, inspecting and testing
REST API services.
"""
server_url = None
""" Server URL """
enable_ssl_verification = True
""" Enable SSL certificates' verification (default: True) """
print_url = False
""" Print URLs during test run """
print_payload = False
""" Print payload sent to the server during test run """
print_headers = False
""" Print requests' headers during test run """
maxDiff = None
session = None
response = None
def _apply_path(self, json_dict, path):
result = None
try:
result = apply_path(json_dict, path)
except WooperAssertionError as e:
self.fail("Path can't be applied: {exception}."
.format(exception=e.args))
else:
return result
def get_url(self, uri):
# get current base URL
return self.server_url.rstrip('/') + uri
def request(self, method, uri, *args,
headers=None, add_server=True, **kwargs):
if not self.session:
self.session = Session()
if add_server:
url = self.get_url(uri)
else:
url = uri
if self.print_url:
print('{method} {url}'.format(method=method, url=url))
if self.print_payload and 'data' in kwargs:
pprint(kwargs['data'])
if self.print_headers:
pprint(headers)
self.response = self.session.request(
method, url, *args,
verify=self.enable_ssl_verification, headers=headers, **kwargs
)
def request_with_data(self, method, uri, *args, data='', **kwargs):
if isinstance(data, dict) or isinstance(data, list):
data = json.dumps(data)
self.request(method, uri, *args, data=data, **kwargs)
[docs] def GET(self, *args, **kwargs):
"""
make a GET request to some URI
:param str uri: URI
rest of args is the same as in requests.get()
"""
self.request('GET', *args, **kwargs)
[docs] def POST(self, *args, **kwargs):
"""
make a POST request to some URI
:param str uri: URI
:param data: request payload
:type data: str, list, dict
rest of args is the same as in requests.post()
"""
self.request_with_data('POST', *args, **kwargs)
[docs] def PATCH(self, *args, **kwargs):
"""
make a PATCH request to some URI
:param str uri: URI
:param data: request payload
:type data: str, list, dict
rest of args is the same as in requests.patch()
"""
self.request_with_data('PATCH', *args, **kwargs)
[docs] def PUT(self, *args, **kwargs):
"""
make a PUT request to some URI
:param str uri: URI
:param data: request payload
:type data: str, list, dict
rest of args is the same as in requests.put()
"""
self.request_with_data('PUT', *args, **kwargs)
[docs] def DELETE(self, *args, **kwargs):
"""
make a DELETE request to some URI
:param str uri: URI
rest of args is the same as in requests.delete()
"""
self.request('DELETE', *args, **kwargs)
@property
def json_response(self):
"""
:returns: response as json
:throws ValueError: if response is not a valid json
"""
try:
return json.loads(self.response.text)
except ValueError:
self.fail('Response in not a valid JSON.')
def inspect_json(self, path=None):
json_response = self._apply_path(self.json_response, path)
pprint(json_response)
def inspect_body(self):
pprint(self.response.text)
def inspect_status(self):
print(self.response.status_code)
def inspect_headers(self):
pprint(dict(self.response.headers))
[docs] def expect_status(self, code):
"""
checks if response status equals given code
:param int code: Expected status code
"""
expect_status(self.response, code)
[docs] def expect_status_in(self, codes):
"""
checks if response status equals to one of the provided
:param list codes: List of valid status codes
"""
expect_status_in(self.response, codes)
[docs] def expect_json(self, expected_json, path=None):
"""
checks if json response equals some json,
:param expected_json: JSON object to compare with
:type expected_json: str, list, dict
:param path: Path inside response json,
separated by slashes, ie 'foo/bar/spam', 'foo/[0]/bar'
:type path: str, optional
"""
expect_json(self.response, expect_json, path)
[docs] def expect_json_match(self, expected_json, path=None):
"""
checks if json response partly matches some json,
:param expected_json: JSON object to compare with
:type expected_json: str, list, dict
:param path: Path inside response json,
separated by slashes, ie 'foo/bar/spam', 'foo/[0]/bar'
:type path: str, optional
"""
expect_json_match(self.response, expected_json, path)
[docs] def expect_json_contains(self, expected_json, path=None,
reverse_expectation=False):
"""
checks if json response contains some json subset,
:param expected_json: JSON object to compare with
:type expected_json: str, list, dict
:param path: Path inside response json,
separated by slashes, ie 'foo/bar/spam', 'foo/[0]/bar'
:type path: str, optional
"""
expect_json_contains(self.response, expected_json, path,
reverse_expectation)
[docs] def expect_json_not_contains(self, expected_json, path=None):
"""
checks if json response not contains some json subset,
:param expected_json: JSON object to compare with
:type expected_json: str, list, dict
:param path: Path inside response json,
separated by slashes, ie 'foo/bar/spam', 'foo/[0]/bar'
:type path: str, optional
"""
expect_json_contains(self.response, expected_json, path,
reverse_expectation=True)
[docs] def expect_json_length(self, length, path=None):
"""
checks if count of objects in json response equals provided length,
:param int length: Expected number of objects inside json
or length of the string
:param path: Path inside response json,
separated by slashes, ie 'foo/bar/spam', 'foo/[0]/bar'
:type path: str, optional
"""
expect_json_length(self.response, length)
[docs] def expect_body_contains(self, text):
"""
checks if response body contains some text
:param str text: Expected text
"""
expect_body_contains(self.response, text)
class ApiTestCase(TestCase, ApiMixin):
pass