即使在使用 "http.client" Python 时引发异常,AssertRaises 也会失败

AssertRaises fails even exception is raised when using "http.client" Python

我正在为使用库 http.Client.

的项目创建 API class

问题出在单元测试上,当我尝试引发错误并使用 assertRaises 对其进行测试时,即使引发了正确的异常,assertRaises 也会失败。

我有一个模块'Foo'。

+---Foo
¦    __init__.py
¦    api.py

我的 __init__.py 里面只有我的例外 classes 和装饰器

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

我的 api.py 包含我的 API class 我正在测试获取访问令牌的身份验证方法

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'],

        )

现在我正在尝试测试我的身份验证是否在获得无效凭据时引发正确的错误。 这是我的测试脚本

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()

正如您在我的测试中看到的那样,我已经尝试了两种方法用于 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)

即使引发了正确的异常,assertRaises 也会失败。 这是我在 运行 单元测试后从终端获得的日志:

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

来自这个问题:AssertRaises fails even though exception is raised

他似乎和我有同样的问题,但他从 2 个不同的路径导入错误,其中对我来说,我很确定我的异常是从我的 __init__.py 在我的 Foo 包上。

真的希望有人能帮助我。我真的觉得我在这里忽略了一些东西。

非常感谢!

如果您阅读堆栈跟踪,它会在这一行引发:

        api = API(*self.invalid_credentials)

assertRaises 之前的一行,因此不会被它捕获

这是因为API.__init__本身调用了self.authenticate()