Reputation: 123
I'm creating an API class for my project that uses the library http.Client.
The issue is with unittesting, When I am trying to raise an error, and test it with assertRaises, the assertRaises fails even with correct Exception being raised.
I have a module 'Foo'.
+---Foo
¦ __init__.py
¦ api.py
inside my __init__.py are just my Exception classes and decorators
import errno
import json
import os
import signal
from dataclasses import dataclass
from functools import wraps
from http.client import HTTPMessage, HTTPException
from typing import Optional
@dataclass
class APIResponse:
method: str
code: int
reason: str
length: Optional[int]
headers: HTTPMessage
message: HTTPMessage
@dataclass
class Token:
access_token: str
expires_in: int
token_type: str
@dataclass
class ClientCredentials:
client_id: str
client_secret: str
audience: str
grant_type: str = "client_credentials"
class ClientCredentialsEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, ClientCredentials):
return o.__dict__
return json.JSONEncoder.default(self, o)
class AuthenticationError(HTTPException):
pass
def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
def decorator(func):
def _handle_timeout(signum, frame):
raise TimeoutError(error_message)
def wrapper(*args, **kwargs):
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(seconds)
try:
result = func(*args, **kwargs)
finally:
signal.alarm(0)
return result
return wraps(func)(wrapper)
return decorator
my api.py consist my API class which I'm testing authenticate method to get Access token
import http.client
import json
import logging
from Foo import ClientCredentials, Token, ClientCredentialsEncoder, AuthenticationError, timeout
from settings import config, API_HOST, AUTH_DOMAIN
class API:
def __init__(self, auth0_domain, client_credentials: ClientCredentials):
self.auth0_domain: str = auth0_domain
self.client_credentials = client_credentials
self._session = http.client.HTTPSConnection(self.auth0_domain)
self.token: Token = self.authenticate()
@property
def session(self):
return self._session
@timeout(10, "API Takes too long to respond.")
def api_request(self, method: str, url: str, body: str, headers: dict):
logging.debug(f"Senging {method} request to {url} for data: {body}...")
self.session.request(method=method, url=url, body=body, headers=headers)
res = self.session.getresponse()
return res
def authenticate(self) -> Token:
logging.debug("Getting API authentiation...")
method = "POST"
url = "/oauth/token"
body = json.dumps(
obj=self.client_credentials,
cls=ClientCredentialsEncoder
)
headers = {'content-type': "application/json"}
response = self.api_request(method=method, url=url, body=body, headers=headers)
status = response.status
reason = response.reason
data = response.read()
json_dict = json.loads(data)
self._session.close()
if status == 403:
logging.error(f"Error {status}: {reason}. Authentication fail.")
raise AuthenticationError(
f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
)
if status == 401:
logging.error(f"Error {status}: {reason}. Authentication fail.")
raise AuthenticationError(
f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
)
logging.info(f"Response {status}. Authentication successful!")
return Token(
access_token=json_dict['access_token'],
expires_in=json_dict['expires_in'],
token_type=json_dict['token_type'],
)
Now I am trying to test my authentication if it is raising the right Error if it got invalid credentials. Here is my test script
import logging
import unittest
from typing import Tuple
from Foo import ClientCredentials, Token, AuthenticationError
from Foo.api import API
from settings import config, API_HOST
class MyTestCase(unittest.TestCase):
def setUp(self):
pass
def test_token(self):
api = API(*self.good_credentials)
self.assertIsInstance(api.authenticate(), Token)
def test_invalid_credentials(self):
api = API(*self.invalid_credentials)
# self.assertRaises(AuthenticationError, api.authenticate)
with self.assertRaises(AuthenticationError) as error_msg:
api.authenticate()
self.assertEqual(error_msg, "Invalid Credentials. Error: 'access_denied', Description: Unauthorized")
@unittest.skip("Can't get the right assertRaises")
def test_invalid_auth_domain(self):
api = API(*self.invalid_auth_domain)
with self.assertRaises(TimeoutError) as e:
api.authenticate()
self.assertEqual(e, "API Takes too long to respond.")
# self.assertRaises(TimeoutError, api2.authenticate())
@property
def good_credentials(self) -> Tuple[str, ClientCredentials]:
auth0_domain = "my.auth.domain"
credentials = ClientCredentials(
client_id=config.read_param('api.value.client-id'),
# client_id='aw;oieja;wlejf',
client_secret=config.read_param('api.value.client-secret'),
audience=API_HOST,
)
return auth0_domain, credentials
@property
def invalid_auth_domain(self) -> Tuple[str, ClientCredentials]:
auth0_domain = "my.auth.domain"
credentials = ClientCredentials(
client_id=config.read_param('api.value.client-id'),
client_secret=config.read_param('api.value.client-secret'),
audience=API_HOST,
)
return auth0_domain, credentials
@property
def invalid_credentials(self) -> Tuple[str, ClientCredentials]:
auth0_domain = "my.auth.domain"
credentials = ClientCredentials(
# client_id=config.read_param('api.value.client-id'),
client_id='aw;oieja;wlejf',
client_secret=config.read_param('api.value.client-secret'),
audience=API_HOST,
)
return auth0_domain, credentials
if __name__ == '__main__':
unittest.main()
As you can see in my test, I've tried it both ways for assertRaises:
# Trial 1
with self.assertRaises(AuthenticationError) as e:
api.authenticate()
self.assertEqual(error_msg, "Invalid Credentials. Error: 'access_denied', Description: Unauthorized")
# Trial 2
self.assertRaises(AuthenticationError, api.authenticate)
The assertRaises fails even with correct Exception being raised. Here is the logs I got from terminal after running the unittest:
Ran 3 tests in 0.842s
FAILED (errors=1, skipped=1)
Error
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
yield
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 615, in run
testMethod()
File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/test/api_authentication_test.py", line 22, in test_invalid_credentials
api = API(*self.invalid_credentials)
File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/Foo/api.py", line 17, in __init__
self.token: Token = self.authenticate()
File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/Foo/api.py", line 66, in authenticate
f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
Foo.AuthenticationError: Invalid Credentials. Error: 'access_denied', Description: Unauthorized
Assertion failed
Assertion failed
Process finished with exit code 1
Assertion failed
Assertion failed
From this problem: AssertRaises fails even though exception is raised
It seems like he has the same issue with me, but he was importing Error from 2 different Paths, wherein for me, I am pretty sure that my Exception was imported from my __init__.py on my Foo package.
Really hope someone can help me. I really feel like there's just something that I am overlooking here.
Thanks a lot!
Upvotes: 0
Views: 459
Reputation: 69964
if you read the stacktrace, it is raising on this line:
api = API(*self.invalid_credentials)
which is one line before the assertRaises
and so it will not be captured by it
this is because API.__init__
itself calls self.authenticate()
Upvotes: 2