How to mock in pytest global variable in imported module - python

I have a module with methods, that I want to test with pytest. In my module I set up global vars such as CONFIG and SESSION etc. to use them in various places.
my_common_module.py, located in /my_project/my_common_module.py
import yaml
import os
import requests
from requests.auth import HTTPBasicAuth
def get_data(url):
response = SESSION.get(url)
if response.status_code == 200:
assets = response.json()
else:
raise RuntimeError(f'ERROR during request: {url}')
return assets
def read_config(path):
with open(path, 'r') as yaml_file:
return yaml.safe_load(yaml_file)
CONFIG = read_config(os.sys.argv[1])
SESSION = requests.Session()
SESSION.auth = HTTPBasicAuth(os.environ['USER'], os.environ['PASS'])
Run command:
python my_common_module.py config.yaml
test_my_common_module.py located in /my_project/tests/test_my_common_module.py
from my_common_module import read_config, SESSION
import pytest
def test_get_data_success(monkeypatch):
class MockResponse(object):
def __init__(self):
self.status_code = 200
#staticmethod
def json(self):
return {'name': 'value'}
def mock_get():
return MockResponse()
url = 'https://testurl.com'
monkeypatch.setattr(SESSION, 'get', mock_get)
assert get_all_assets(url) == {'name': 'value'}
When I run:
pytest tests
I got this error:
tests/test_my_common_module.py:1: in <module>
from my_common_module import get_data, SESSION
my_common_module.py:20: in <module>
CONFIG = read_config(os.sys.argv[1])
my_common_module:16: in read_config
with open(path, 'r') as yaml_file:
E IsADirectoryError: [Errno 21] Is a directory: 'tests'
==========================short test summary info ==================================
ERROR tests/test_my_common_module.py - IsADirectoryError: [Errno 21] Is a directory: 'tests'
How can I mock or patch CONFIG os.sys.argv[1] or the whole method read_config, before I imported my_commons_module in test_my_common_module.py and avoid this error?

Related

FastAPI - Use TestClient - Loading app fails to missing ENV Variables

I have the following main.py:
...
app = FastAPI(docs_url="/jopapi/documentation", redoc_url=None)
password_encryptor = PasswordEncryptor(
os.environ.get("JUPYTERHUB_COOKIE_SECRET"), fernet=Fernet
)
...
I already tried to use a custom fixture like this:
#mock.patch.dict(os.environ, {"JUPYTERHUB_COOKIE_SECRET": "471bAcmHjbIdu3KLWphYpgXSW1HNC8q7"}, clear=True)
#pytest.fixture(name="client")
def client_fixture():
client = TestClient(app)
yield client
But the following test:
def test_generic(client: TestClient):
response = client.get("/jobapi/generic")
assert response.status_code == 200
still fails, since the environment variable seems not to get set, which results in None
from src.app.main import app
src\app\main.py:38: in <module>
password_encryptor = PasswordEncryptor(
src\app\auth.py:33: in __init__
self.fernet = fernet(self._generate_fernet_key(self.secret))
src\app\auth.py:36: in _generate_fernet_key
bytestring = secret.encode()
E AttributeError: 'NoneType' object has no attribute 'encode'
When I set the env variable in the main.py file per hand, it works. How can I fix this?
I think you need to create a .env at the root of your project and then read that file using python's dotenv, so finally you can easily use it with os.environ.get() later in your main.py file,
.env
JUPYTERHUB_COOKIE_SECRET=471bAcmHjbIdu3KLWphYpgXSW1HNC8q7
main.py
import os
basedir = path.abspath(path.dirname(__file__))
load_dotenv(path.join(basedir, ".env"))
app = FastAPI(docs_url="/jopapi/documentation", redoc_url=None)
password_encryptor = PasswordEncryptor(
os.environ.get("JUPYTERHUB_COOKIE_SECRET"), fernet=Fernet
)

Python mock.patch() not mocking imported class module

I have a some code that looks like this (abridged):
## ./module_1/lambda_function.py
from shared.graphql_helpers import GraphQLClient
gql_client = GraphQLClient('http://host_url.test/')
post_event_to_consumer(event):
response = gql_client.make_query(mutation, { 'data': event, {})
lambda_hanbdler(event, context):
post_event_to_consumer(event['detail']
The module imported is such:
## ./shared/graphql_helpers.py
import requests
class GraphQLClient:
def __init__(self, url):
self.host_url = url
def make_query(self, query, variables, headers):
request = requests.post(self.host_url, json={'query': query, 'variables': variables}, headers=headers)
if request.status_code != 200:
raise Exception()
elif request.json()['errors']:
raise Exception()
else:
return request.json()['data']
The project structure is such:
module_1
__init__.py
lambda_function.py
lambda_function_test.py
shared
__init__.py
graphql_helpers.py
My problem is that I am trying to patch GraphQLClient in lambda_function_test.py but it does not seem to be working.
My unit test looks like this:
## ./module_1/lambda_function_test.py
import os
from unittest import mock, TestCase
class TestLambdaFunction(TestCase):
#mock.patch('module_1.lambda_function.GraphQLClient')
def test_post_event_to_compplex(self, mock_gql_client):
mock_gql_client = mock.Mock()
mock_gql_client.make_query.return_value = {}
result = post_event_to_consumer(json_data['detail'])
assert result == None
From what I read online, patch mocks the import in the SUT's namespace, however, when I run the test, it imports the original GraphQLClient and throws a ConnectionError. I also tried removing module_1 from the path but that didn't work either.
Not sure what's incorrect here, but any help would be appreciated.

Unit test for Python AWS Lambda: mock function before module is imported

I'm trying to write unit tests for my aws lambda function written in python 3.9. I tried different things to mock the get_object function that makes calls to the S3. I wanted to focus only on the calculate method, to verify if I'm getting correct results of an calculation.
When I try to run the following approach I'm getting credential errors about boto3
python -m unittest tests/app-test.py
...
botocore.exceptions.NoCredentialsError: Unable to locate credentials
Is there a way to import the calculate method from app.py and mock call to the get_object fn?
directory:
functions:
- __init__.py
- app.py
tests:
- __init__.py
- app-test.py
lambda function app.py:
import json
import boto3
def get_object():
s3 = boto3.client('s3')
response = s3.get_object(Bucket='mybucket', Key='object.json')
content = response['Body'].read().decode('utf-8')
return json.loads(content)
stops = get_object()
def lambda_handler(event, context):
params = event['queryStringParameters']
a = int(params['a'])
b = int(params['b'])
result = calculate(a, b)
return {
'statusCode': 200,
'body': json.dumps(result)
}
def calculate(a, b):
return a + b
unit test app-test.py:
import unittest
from unittest import mock
with mock.patch('functions.app.get_object', return_value={}):
from functions.app import calculate
class TestCalculation(unittest.TestCase):
def test_should_return_correct_calculation(self):
# when
result = calculate(1, 2)
# then
self.assertEqual(3, result)
I was able to fix the issue. The biggest obstacle was to mock the boto3 in the app.py. I did this, by mocking the whole boto3 module before it's imported. Here's the code of app-test.py
import sys
from io import BytesIO
from json import dumps
from unittest import TestCase, main
from unittest.mock import Mock
from botocore.stub import Stubber
from botocore.session import get_session
from botocore.response import StreamingBody
# prepare mocks for boto3
stubbed_client = get_session().create_client('s3')
stubber = Stubber(stubbed_client)
# mock response from S3
body_encoded = dumps({'name': 'hello world'}).encode()
body = StreamingBody(BytesIO(body_encoded), len(body_encoded))
stubbed.add_response('get_object', {'Body': body})
stubber.activate()
# add mocks to the real module
sys.modules['boto3'] = Mock()
sys.modules['boto3'].client = Mock(return_value=stubbed_client)
# Import the module that will be tested
# boto3 should be mocked in the app.py
from functions.app import calculate
class TestCalculation(TestCase):
def test_should_return_correct_calculation(self):
# when
result = calculate(1, 2)
# then
self.assertEqual(3, result)

What is the cause of this Attribute Error?

I have 2 modules as:
main.py
get.py
In main.py, I need to call a function present in get.py which is call_get().
So I'm trying it this way:
import get
get.call_get()
But it throws an error as 'Attribute Error'.
On the other hand, the following code works:
import temp
temp.func()
function func in temp.py looks as:
import get
get.call_get()
I am unable to understand this behaviour. Please guide.
get.py code:
import requests
import json
import sys
import utils
import error_handler
import get_helper
import os
import pvdata
def call_get():
try:
auth_tuple = utils.get_auth_details("get")
headers = utils.get_header()
resource_types = utils.get_resource_types()
namespaces = utils.get_namespaces()
if resource_types[0].lower() == "all":
resource_types = utils.append_all_resources()
get_helper.get_namespace_list(auth_tuple, headers)
all_namespaces = utils.extract_namespaces_from_list("source")
if namespaces[0].lower() != "all":
error_handler.validate_source_namespaces(namespaces, all_namespaces)
utils.create_file(
"Namespaces", "All_namespaces_at_source.txt", str(all_namespaces))
get_helper.generate_json_for_all_namespaces(
all_namespaces, auth_tuple, headers)
for resource_name in resource_types:
if namespaces[0].lower() == "all":
for namespace in all_namespaces:
get_helper.call_all_functions_for_get(
namespace, resource_name, headers, auth_tuple)
else:
for namespace in namespaces:
get_helper.call_all_functions_for_get(
namespace, resource_name, headers, auth_tuple)
except Exception as error:
filename = os.path.basename(__file__)
error_handler.print_exception_message(error, filename)
return
if __name__ == "__main__":
call_get()
main.py code:
import utils
import remote_exec
import post
import get
import error_handler
import os
import handle_space
import socket
import json
from requests import get
import sys
import temp
def only_dest_requires_jumpserver():
try:
dictionary = {
"migration_type": utils.config_data()["source_cloud"] + " to " + utils.config_data()["dest_cloud"]
}
utils.update_config_file(dictionary)
print("\nInitialising " + utils.config_data()["source_cloud"] + " to " + utils.config_data()["dest_cloud"] + " migration...")
hostname = socket.gethostname()
if hostname == utils.config_data()["my_hostname"]:
# get.call_get()
temp.func()
print("\nData successfully exported from source to this machine.\nChecking for space availability at jumpserver...")
print("Done!")
except Exception as error:
filename = os.path.basename(__file__)
error_handler.print_exception_message(error, filename)
The issue is main.py has 2 get modules as:
import get
from requests import get
get is being overwritten.....you need to rename your function or use
import request
request.get
Another simple way is aliasing suggested by InsertCheesyLine.
from requests import get as _get
and use _get

How to make the HTMLTestRunner.py pythonfilename > result.html line inside python file to automatically create the html file?

I have a Python file named pythontc.py which consists of the below code:
import unittest
import genson
import requests,json
from requests.auth import HTTPBasicAuth
from jsonschema import validate
class TC1(tu.Tc):
def setUp(self):
with open('schema.json', 'q') as t:
self.schema = t.read()
self.file = t
def test1(self):
result =requests.get('http://localhost:8000/some/123/somename?attribute=somename&someid=123&provider=somename&refresh=true', auth=HTTPBasicAuth('USERNAME', 'Pass'))
data = result.text
print data
def tearDown(self):
self.file.close()
if __name__ == '__main__':
tu.main()
Now this exexutes in cmd now i want to get that result in html format using htmltest runner.
I used the command in cmd as:
cmd>HTMLTestRunner.py pythontc >result.html
But i dont want to type manually every time so what can i add in python code ?

Categories

Resources