I'm having difficulty in writing test cases for the ZabbixAPILayer class mentioned below. I'm not sure how I can mock the 'zabbix_conn_obj' there. Any help will be appreciated. Thanks!
file: externalapi/apilayer.py
from zabbix.api import ZabbixAPI
import json
import time
class ZabbixAPILayer(object):
def uptime(self,arg,zabbix_conn_obj):
try:
getUpdateItem = zabbix_conn_obj.do_request("item.get", {"host":arg})
lastclock=getUpdateItem['result'][37].get('lastclock')
lastclock=int(lastclock)
curclock=int(time.time())
check_val=curclock-lastclock
limit=60*1000
if check_val<limit:
lastval=getUpdateItem['result'][37].get('lastvalue')
return time.strftime("%H:%M:%S", time.gmtime(float(getUpdateItem['result'][37].get('lastvalue'))))
else:
return "-"
except:
return "NOT AVAILABLE"
.....
class APILayer(ZabbixAPILayer):
def __init__(self):
self.zabbix_conn_obj=ZabbixAPI(url=settings.ZABBIX_URL, user=settings.ZABBIX_USER, password=settings.ZABBIX_PWD)
def uptime(self,arg):
return super(APILayer,self).uptime(arg,self.zabbix_conn_obj)
.....
file: base/admin.py
......
from ..externalapis.apilayer import APILayer
......
gen_obj= APILayer()
gen_obj.uptime()
......
Thanks for your comments. Got this working! Here the way i'm doing this
import mock
...
def test_uptime(self):
zabbix_conn_obj = mock.Mock()
json_blob = {} # This is the json blob i'm using as return value from do request
zabbix_conn_obj.do_request = mock.Mock(return_value=json_blob)
obj = ZabbixAPILayer()
value = obj.uptime("TestHOST",zabbix_conn_obj)
desired_result = '' #whatever my desired value is
self.assertEqual(value, desired_result)
Related
I am currently trying to write unit tests for my python code using Moto & #mock_dynamodb2 . So far it's been working for me to test my "successful operation" test cases. But I'm having trouble getting it to work for my "failure cases".
In my test code I have:
#mock_dynamodb2
class TestClassUnderTestExample(unittest.TestCase):
def setUp(self):
ddb = boto3.resource("dynamodb", "us-east-1")
self.table = ddb.create_table(<the table definition)
self.example_under_test = ClassUnderTestExample(ddb)
def test_some_thing_success(self):
expected_response = {<some value>}
assert expected_response = self.example_under_test.write_entry(<some value>)
def test_some_thing_success(self):
response = self.example_under_test.write_entry(<some value>)
# How to assert exception is thrown by forcing put item to fail?
The TestClassUnderTestExample would look something like this:
class ClassUnderTestExample:
def __init__(self, ddb_resource=None):
if not ddb_resource:
ddb_resource = boto3.resource('dynamodb')
self.table = ddb_resource.Table(.....)
def write_entry(some_value)
ddb_item = <do stuff with some_value to create sanitized item>
response = self.table.put_item(
Item=ddb_item
)
if pydash.get(response, "ResponseMetadata.HTTPStatusCode") != 200:
raise SomeCustomErrorType("Unexpected response from DynamoDB when attempting to PutItem")
return ddb_item
I've been completely stuck when it comes to actually mocking the .put_item operation to return a non-success value so that I can test that the ClassUnderTestExample will handle it as expected and throw the custom error. I've tried things like deleting the table before running the test, but that just throws an exception when getting the table rather than an executed PutItem with an error code.
I've also tried putting a patch for pydash or for the table above the test but I must be doing something wrong. I can't find anything in moto's documentation. Any help would be appreciated!
The goal of Moto is to completely mimick AWS' behaviour, including how to behave when the user supplies erroneous inputs. In other words, a call to put_item() that fails against AWS, would/should also fail against Moto.
There is no build-in way to force an error response on a valid input.
It's difficult to tell from your example how this can be forced, but it looks like it's worth playing around with this line to create an invalid input:
ddb_item = <do stuff with some_value to create sanitized item>
Yes, you can. Use mocking for this. Simple and runnable example:
from unittest import TestCase
from unittest.mock import Mock
from uuid import uuid4
import boto3
from moto import mock_dynamodb2
def create_user_table(table_name: str) -> dict:
return dict(
TableName=table_name,
KeySchema=[
{
'AttributeName': 'id',
'KeyType': 'HASH'
},
],
AttributeDefinitions=[
{
'AttributeName': 'id',
'AttributeType': 'S'
},
],
BillingMode='PAY_PER_REQUEST'
)
class UserRepository:
table_name = 'users'
def __init__(self, ddb_resource):
if not ddb_resource:
ddb_resource = boto3.resource('dynamodb')
self.table = ddb_resource.Table(self.table_name)
def create_user(self, username):
return self.table.put_item(Item={'id': str(uuid4), 'username': username})
#mock_dynamodb2
class TestUserRepository(TestCase):
def setUp(self):
ddb = boto3.resource("dynamodb", "us-east-1")
self.table = ddb.create_table(**create_user_table('users'))
self.test_user_repo = UserRepository(ddb)
def tearDown(self):
self.table.delete()
def test_some_thing_success(self):
user = self.test_user_repo.create_user(username='John')
assert len(self.table.scan()['Items']) == 1
def test_some_thing_failure(self):
self.test_user_repo.table = table = Mock()
table.put_item.side_effect = Exception('Boto3 Exception')
with self.assertRaises(Exception) as exc:
self.test_user_repo.create_user(username='John')
self.assertTrue('Boto3 Exception' in exc.exception)
I am a beginner when it comes to writing tests and mocking.
I have a created two modules. One module object (Site) creates another object from my second module (Item) on init. The Item object makes a call to an API endpoint to get some data using requests.
I want to Mock the API call I am making so I can test things like a bad response and importantly have control over the response data.
I have simplified my code and put below. When I run the test I get back the actual response data and not what I have mocked.
I have a feeling I am not putting my Mock in the right place. Also, I have seen lots of people saying to use #unittest.patch annotation. I am not clear if I should be using that here.
So I am looking for how to get _get_range_detail to actually return a Mocked response from requests and also just general feedback on if it looks like I am approaching this the right way.
# hello_world.py
from mymodule.site import Site
sites = [
dict(
name="site1",
ranges=[
"range1",
"range2"
]
)
]
site_object = Site(sites[0]['name'], sites[0]['ranges'])
for i in site_object.get_ranges_objects():
print(i.range_detail)
# site.py
from mymodule.item import Item
class Site:
def __init__(self, name, ranges):
self.name = name
self.ranges = ranges
self.ranges_objects = []
for my_range in ranges:
self.ranges_objects.append(Item(my_range))
def get_ranges_objects(self):
return self.ranges_objects
# item.py
import requests
class Item:
def __init__(self, range_name):
self.range_name = range_name
self.range_detail = self._get_range_detail(self.range_name)
def _get_range_detail(self, range_name):
uri = "https://postman-echo.com/get?some_cool_value=real_value"
try:
r = requests.get(uri)
if r.status_code == 200:
return r.json()['args']
else:
return None
except Exception as e:
print(e)
exit(1)
# test_site.py
import pytest
from mymodule.site import Site
from unittest import mock
from mymodule.item import requests
def test_get_ranges_objects():
sites = [
dict(
name="site1",
ranges=[
"range1",
"range2"
]
)
]
requests = mock.Mock()
requests.status_code = 200
requests.json.return_value = {
'args': {'some_mock_value': 'mocky'}
}
site_object = Site(sites[0]['name'], sites[0]['ranges'])
assert site_object.name == "site1"
assert isinstance(site_object.ranges_objects, list)
assert site_object.ranges_objects[0].range_detail == dict(some_mock_value='mocky')
You can use pytest-mock. it makes mocking in pytest simple. (pip install pytest-mock)
You should replace requests.get. Simply
requests_get = mock.patch('requests.get').start()
If you use pytest-mock,
requests_get = mocker.patch('requests.get')
Rewrote test case using pytest-mock
# test_site.py
import pytest
from mymodule.site import Site
from unittest import mock
#pytest.fixture
def requests_get(mocker):
requests_get = mocker.patch('requests.get')
yield requests_get
def test_get_ranges_objects(mocker, requests_get):
response = mocker.MagicMock()
response.status_code = 200
response.json.return_value = {'args': {'some_mock_value': 'mocky'}}
requests_get.return_value = response
sites = [
dict(
name="site1",
ranges=[
"range1",
"range2"
]
)
]
site_object = Site(sites[0]['name'], sites[0]['ranges'])
assert site_object.name == "site1"
assert isinstance(site_object.ranges_objects, list)
assert site_object.ranges_objects[0].range_detail == {'some_mock_value': 'mocky'}
My flask app is telling me that the method of a class I created is not defined. It says:
AttributeError: 'GetIndexItemInfo' object has no attribute 'genDFs'
Here is the code:
__init__.py
#app.route('/assistant', methods=['GET', 'POST'])
#login_required
def use_assistant():
from flask import request
if request.method == 'GET':
...FORM GOES HERE
else:
report = request.form.get('report')
from modules.majestic import config
rprt = config[report]
inst=rprt(request.form)
url = inst.genUrl()
dic = inst.getData(url)
df = inst.genDFs(dic)
...
majestic.py
import urllib.request, urllib.parse, urllib.error
import ast
import pandas as pd
import ssl
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
class Majestic:
key='APIKEYHERE'
base='https://api.majestic.com/api/json?app_api_key={}'.format(key)
#staticmethod
def getData(url):
req = urllib.request.Request(url=url)
f = urllib.request.urlopen(req, context=ctx)
x = f.read().decode('utf-8')
dic = ast.literal_eval(x)
return dic
class Report(Majestic):
def __init__(self,data):
items=[]
for key,val in data.items():
if 'address' in key:
items.append(val)
self.items = items
self.len = len(items)
self.cmd = data['cmd']
self.base_url = super().base
def genUrl(self):
substr=self.base_url+'&cmd='+self.cmd+'&items='+str(len(self.items))+'&'
for i,item in enumerate(self.items):
substr=substr+'item'+str(i)+'='+item+'&'
return substr[:-1]
class GetIndexItemInfo(Report):
#staticmethod
def genDfs(data):
for i in range(len(data['DataTables']['Results']['Data'])):
new_data=data['DataTables']['Results']['Data'][i]
cols = ['Url','AC Rank','Citation Flow','Trust Flow','Ext. Back Links','Ref Domains','Ref Follow Domains']
maj_cols = ['Item','ACRank','CitationFlow','TrustFlow','ExtBackLinks','RefDomains','RefDomainTypeFollow']
dic = dict(zip(cols,[new_data[i] for i in maj_cols]))
cols = ['Url','AC Rank','Citation Flow','Trust Flow','Ext. Back Links','Ref Domains','Ref Follow Domains']
if i == 0:
main_df = pd.DataFrame(dic,index=[0])[cols]
else:
df = pd.DataFrame(dic,index=[0])[cols]
main_df = main_df.append(df, ignore_index=True)
return main_df
I have tested majestic.py seperately and the class instance of GetIndexItemInfo I create works as expected and does not return the error.
Any ideas why i might be getting this error when i run it in flask?
Many thanks in advance
It is correct.
AttributeError: 'GetIndexItemInfo' object has no attribute 'genDFs'
Your method is called genDfs
Notice the capitalisation.
I have module billing/billing-collect-project-license which have LicenseStatistics class. Which have calls to Redis, ORMRDS, CE are other modules which used in this class. Following is LicenseStatistics class where get_allocate_count is instance method which calls to ce_obj.get_set_ce_obj, get_license_id and many others.
The get_license_id method calls get_customer_details.
class LicenseStatistics():
"""This class encapsulates all logic related to license usage."""
def __init__(self):
self.logger = LOGGER
# RedisNode List should be in the following format:
# HOST1:PORT1,HOST2:PORT2,HOST3:PORT3 etc
redis_node_list = os.environ.get('REDIS_NODE_LIST', '').split(',')
self.redis_utils = RedisUtils(redis_node_list)
# DB object to read customer details
dbhost = os.environ.get('DBHOST')
dbuser = os.environ.get('DBUSER')
dbpass = os.environ.get('DBPASSWORD')
dbname = os.environ.get('DBNAME')
dbport = os.environ.get('DBPORT')
self.dbutils = ORMDBUtils(dbhost, dbuser, dbpass, dbname, dbport)
self.ce_obj = CE()
self.bill = Billing()
def get_license_id(self, project_id):
"""
#Summary: Get License Id for customer/project from customer table by
project id
#param project_id (string): CE project Id
#return (string): CE License Id which associate with Project.
"""
# Get license ID from RDS
customer_details = self.get_customer_details(project_id)
print("customer_details:", customer_details)
license_id = customer_details["license_id"]
if not license_id:
msg = "No license for project {}".format(project_id)
self.logger.error(msg)
raise InvalidParamException(msg)
print("license_id:", license_id)
return license_id
def get_customer_details(self, project_id):
"""
#Summary: Get Customer/Project details from customer table
#param project_id (string): CE project Id
#return (dictionary): Customer details from customer table.
"""
filters = {"project_id": project_id}
customer_details = self.dbutils.get_items(
table_name=RDSCustomerTable.TABLE_NAME.value,
columns_to_select=["account_id", "license_id"],
filters=filters
)
if not customer_details:
msg = "No customer found for project {}".format(project_id)
self.logger.error(msg)
raise NoCustomerException(msg)
return customer_details[0]
def is_shared_license(self, license_id):
# This function return True or False
pass
def get_project_machines_count(self, project_id, license_id):
# This function return number of used license.
count = 20
return count
def get_license_usage(self, project_id, license_id):
# This function return number of machines used project.
count = 10
return count
def get_allocate_count(self, project_id):
"""
#Summary: Get number of licenses are used by Project.
#param project_id (string): CloudEndure Project Id.
#return (int): Number of license are used in Project.
"""
# set Session get_customer_detailsfrom Redis
status = self.ce_obj.get_set_ce_obj(
project_id=project_id, redis_utils=self.redis_utils
)
print("license_id status--:", status)
if not status:
msg = "Unable to set CEproject {}".format(project_id)
self.logger.critical(msg)
raise InvalidParamException(msg, "project_id", project_id)
print("project_id------------:", project_id)
# Get license Id
license_id = self.get_license_id(project_id)
print("license_id:", license_id)
# Check license is shared
shared_flag = self.is_shared_license(license_id)
if not shared_flag:
# Get license usage
licenses_used = self.get_license_usage(project_id, license_id)
else:
# Get machine account
licenses_used = self.get_project_machines_count(
project_id, license_id
)
return licenses_used
I am writing unit test for get_allocate_count method, I mock the Redis, ORMRDS, Custom Exception, Logger.
This function call ce_obj.get_set_ce_obj function which return True/False. I am to mock/patch return value of this function successfully.
But when call goes to next function call i.e. get_license_id, call goes into actual function call and due to improper inputs. I am not able to patch/mock
Following is unit test code:
import responses
import unittest
from unittest.mock import patch
import os
import sys
cwd_path = os.getcwd()
sys.path.append(cwd_path)
sys.path.append(cwd_path+"/../sam-cfns/code")
sys.path.append(cwd_path+"/../sam-cfns/code/billing")
from unit_tests.common.mocks.env_mock import ENV_VAR
from unit_tests.common.mocks.logger import FakeLogger
from unit_tests.common.mocks.cache_mock import RedisUtilsMock
from unit_tests.common.mocks.ormdb_mock import ORMDBUtilsMockProject
from unit_tests.common.mocks.exceptions_mock import NoCustomerExceptionMock
from unit_tests.common.mocks.exceptions_mock import BillingExceptionMock
from unit_tests.common.mocks.exceptions_mock import InvalidParamExceptionMock
from unit_tests.common.mocks.api_responses import mock_response
from unit_tests.common.examples import ce_machines_data
from unit_tests.common.examples import ce_license_data
from unit_tests.common.examples import ce_data
class BillingTest(unittest.TestCase):
""" Billing TEST class drive from UnitTest """
#patch("billing-collect-project-license.Logger", FakeLogger)
#patch("os.environ", ENV_VAR)
#patch("billing-collect-project-license.RedisUtils", RedisUtilsMock)
#patch("billing-collect-project-license.ORMDBUtils", ORMDBUtilsMockProject)
#patch("exceptions.NoCustomerException", NoCustomerExceptionMock)
#patch("billing.billing_exceptions.BillingException", BillingExceptionMock)
#patch("billing.billing_exceptions.InvalidParamException", InvalidParamExceptionMock)
def __init__(self, *args, **kwargs):
"""Initialization"""
super(BillingTest, self).__init__(*args, **kwargs)
billing_collect_project_license_module = (
__import__("cbr-billing-collect-project-license")
)
self.licenses_stats_obj = (
billing_collect_project_license_module.LicenseStatistics()
)
class BillingCollectProjectLicense(BillingTest):
"""Login Unit Test Cases"""
def __init__(self, *args, **kwargs):
"""Initialization"""
super(BillingCollectProjectLicense, self).__init__(*args, **kwargs)
def setUp(self):
"""Setup for all Test Cases."""
pass
##patch("billing.cbr-billing-collect-project-license.LicenseStatistics."
# "get_project_machines_count")
##patch("billing.cbr-billing-collect-project-license.LicenseStatistics."
# "get_customer_details")
##patch("billing.cbr-billing-collect-project-license.LicenseStatistics.get_license_id")
#patch("billing.cbr-billing-collect-project-license.LicenseStatistics.get_license_id")
#patch("cbr.ce.CloudEndure.get_set_ce_obj")
def test_get_allocate_count(self, get_set_ce_obj_mock, get_license_id_mock):
project_id = ce_data.CE_PROJECT_ID
license_id = ce_license_data.LICENSE_ID
get_set_ce_obj_mock.return_value = True
get_license_id_mock.return_value = license_id
# LicenseStatistics_mock.return_value.get_license_id.return_value = license_id
#get_license_id_mock.return_value = license_id
# self.licenses_stats_obj.get_license_id = get_license_id_mock
get_customer_details_mock = {"license_id": license_id}
# is_shared_license_mock.return_value = True
# get_project_machines_count_mock.return_value = 20
resp = self.licenses_stats_obj.get_allocate_count(
project_id
)
self.assertEqual(resp, 20)
if __name__ == '__main__':
unittest.main()
I am not able to patch get_license_id function from same class. This actually calling get_license_id function and fails.
I want to mock return value of get_license_id function.
Anyone help help me?
Thank you.
Issue is I am initializing it in the init, so monkeypatching methods of LicenseStatistics class later have no effect on the already created instance. #hoefling
By monkey Patching I able to run Test Cases successfully.
sample code:
def test_get_allocate_count_ok_4(self, ):
"""
#Summary: Test case for successful response for shared license
by other unittest method - Monkey Patching
"""
def get_customer_details_mp(_):
"""Monkey Patching function for get_customer_details"""
data = [
{
"account_id": "abc",
"project_id": "abc",
"license_id": "abc",
"status": "Active"
}
]
return data
def get_set_ce_obj_mp(_, _tmp):
"""Monkey Patching function for get_set_ce_obj"""
return True
def get_license_id_mp(_):
"""Monkey Patching function for get_license_id"""
return "abc"
def is_shared_license_mp(_):
"""Monkey Patching function for is_shared_license"""
return True
def get_project_machines_count_mp(_, _license_id):
"""Monkey Patching function for get_project_machines_count"""
return 5
project_id = "abc"
# Monkey Patching
self.licenses_stats_obj.get_customer_details = get_customer_details_mp
self.licenses_stats_obj.ce_obj.get_set_ce_obj = get_set_ce_obj_mp
self.licenses_stats_obj.get_license_id = get_license_id_mp
self.licenses_stats_obj.is_shared_license = is_shared_license_mp
self.licenses_stats_obj.get_project_machines_count = (
get_project_machines_count_mp
)
resp = self.licenses_stats_obj.get_allocate_count(project_id)
self.assertEqual(resp, 5)
I'm trying to trap the following error in a try/exception block, but as this is a custom module that is generating the error - not generating a standard error such as ValueError for example. What is the correct way to catch such errors?
Here is my code:
try:
obj = IPWhois(ip_address)
except Exception(IPDefinedError):
results = {}
else:
results = obj.lookup()
The most obvious way:
except IPDefinedError:
gives:
NameError: name 'IPDefinedError' is not defined
The error returned that I want to check for is:
ipwhois.exceptions.IPDefinedError
ipwhois.exceptions.IPDefinedError: IPv4 address '127.0.0.1' is already defined as 'Private-Use Networks' via 'RFC 1918'.
The issue here is the import!
I had the import as
from ipwhois import IPWhois
but I also needed
import ipwhois
So the following works:
try:
obj = IPWhois(ip_address)
except ipwhois.exceptions.IPDefinedError:
results = {}
else:
results = obj.lookup()
Here is a quick recap. Yes, the error in your question did look like it was likely related to an import issue (as per my comment ;) ).
from pprint import pprint as pp
class IPDefinedError(Exception):
"""Mock IPDefinedError implementation
"""
pass
class IPWhois(object):
"""Mock IPWhois implementation
"""
def __init__(self, ip_address):
if ip_address == "127.0.0.1":
raise IPDefinedError(
"IPv4 address '127.0.0.1' is already defined as 'Private-Use Networks' via 'RFC 1918'.")
self._ip_address = ip_address
def lookup(self):
return "RESULT"
def lookup(ip_address):
""" calculates IPWhois lookup result or None if unsuccessful
:param ip_address:
:return: IPWhois lookup result or None if unsuccessful
"""
result = None
try:
obj = IPWhois(ip_address)
result = obj.lookup()
except IPDefinedError as e:
msg = str(e)
print("Error received: {}".format(msg)) # do something with msg
return result
if __name__ == '__main__':
results = map(lookup, ["192.168.1.1", "127.0.0.1"])
pp(list(results)) # ['RESULT', None]