I am trying to unit test Python based aws lambda code. I tried to use patch to mock environment variables but it throws error.How to handle global variables during unit testing. Any help is appreciated
Tried #mock #patch but nothing seems to be working. When executing the testcase the values seems to be empty
[registration.py]
import os
#Global Variables
DEBUG = os.environ.get("EnableLog")
rest_endpoint = os.environ.get('restApiEndpoint')
db_fn_endpoint = rest_endpoint+":3000/rpc/"
def lambda_handler(event,context):
return "success" #to see if this works
if __name__ == "__main__":
lambda_handler('', '')
Unit Test [test_registration.py]
import json
import pytest
import sys, os
from mock import patch
from businesslogic import registration
#mock.patch.dict(os.environ,{'EnableLog':'True'})
#mock.patch.dict(os.environ,{'restApiEndpoint':'http://localhost'})
#patch('registration.restApiEndpoint', 'http://localhost')
def test_lambda_handler():
assert lambda_handler() =="success"
When I run pytest test_registration.py
I get below exception
businesslogic\registration.py:6: in <module>
db_fn_endpoint = rest_endpoint+":3000/rpc/"
E TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
You can pass it as restApiEndpoint=http://localhost python -m pytest
OR
you if you are using sh file to run export before running test
export restApiEndpoint=http://localhost
Related
I am testing a bot in python-telegram-bot using pyrogram to simulate inputs. I have to set a cryptography key in my environment so the code being tested can access it. I tried to accomplish this using the setup-teardown concept inside pytest.fixtures.
For that, i created a file where all my fixtures are created, and one of them is set like #pytest.fixture(autouse=True, scope="session")
The fixture is being found, but not executed. Why?
File structure:
--root
|-tests/
|- conftest.py
|- test_something1.py
|- test_something2.py
I am executing pytest -s --fixtures from the root folder
Here is conftest.py
# coding:utf-8
import os
import pytest
from pyrogram import Client
from telegram.ext import Application
from config import BOT_ID
from contrib.consts import CRYPT_KEY_EDK
from tests.config import TESTER_USERNAME, API_ID, API_HASH, CRYPT_KEY
#pytest.fixture
async def bot():
async with Application.builder().token(BOT_ID).build() as bot:
yield bot
#pytest.fixture
async def pyro_client():
async with Client(name=TESTER_USERNAME, api_id=API_ID, api_hash=API_HASH) as pyro_client:
yield pyro_client
#pytest.fixture(autouse=True, scope="session")
def set_crypt_key():
print("Entered set_crypt_key fixture")
os.environ[CRYPT_KEY_EDK] = CRYPT_KEY
And here is 'test_can_access_netbox.py':
# coding: utf-8
from unittest import mock
import pynetbox
import pytest
import requests
from pynetbox import RequestError
from telegram.ext import ContextTypes
from contrib.consts import NETBOX_CONFIG_CDK
from contrib.datawrappers.netboxconfig import NetboxConfig
from tests.config import NETBOX_TEST_TOKEN, NETBOX_TEST_URL
chat_data_mock = {
NETBOX_CONFIG_CDK: NetboxConfig(token=NETBOX_TEST_TOKEN, url=NETBOX_TEST_URL)
}
#mock.patch("telegram.ext.ContextTypes.DEFAULT_TYPE", chat_data=chat_data_mock)
def test_can_access_netbox(context: ContextTypes.DEFAULT_TYPE):
"""TC002"""
try:
nb = pynetbox.api(**context.chat_data[NETBOX_CONFIG_CDK].to_dict())
nb.status()
except RequestError as e:
pytest.fail("Error requesting connection to Netbox: {}".format(e))
except requests.exceptions.ConnectionError as e:
pytest.fail("Connection error: {}".format(e))
if __name__ == '__main__':
test_can_access_netbox()
And when i run my tests with pytest -s --fixtures, i get this error:
------------------------------------------------------------- fixtures defined from tests.fixtures -------------------------------------------------------------
bot -- tests/fixtures.py:19
pyro_client -- tests/fixtures.py:29
set_crypt_key [session scope] -- tests/fixtures.py:39
============================================================================ ERRORS ============================================================================
_______________________________________________________ ERROR collecting tests/test_can_access_netbox.py _______________________________________________________
tests/test_can_access_netbox.py:16: in <module>
NETBOX_CONFIG_CDK: NetboxConfig(token=NETBOX_TEST_TOKEN, url=NETBOX_TEST_URL)
contrib/datawrappers/netboxconfig.py:8: in __init__
super().__init__()
contrib/classesbehaviors/cryptographs.py:13: in __init__
self.crypt = Fernet(os.environ[CRYPT_KEY_EDK].encode())
/usr/lib/python3.8/os.py:675: in __getitem__
raise KeyError(key) from None
E KeyError: 'TELOPS_CRYPT_KEY'
=================================================================== short test summary info ====================================================================
ERROR tests/test_can_access_netbox.py - KeyError: 'TELOPS_CRYPT_KEY'
======================================================================= 1 error in 0.36s =======================================================================
Notice that my print() did not appear, despite of me using -s on command.
Libs i am using:
pytest==7.2.1
pytest-asyncio==0.20.3
Python is 3.8.10
Here are the first lines of the command output, i hope they help:
platform linux -- Python 3.8.10, pytest-7.2.1, pluggy-1.0.0
rootdir: /home/joao/Desktop/enterprise/project_name
plugins: asyncio-0.20.3, anyio-3.6.2
asyncio: mode=strict
The error messages you pasted don't quite match the snippets you included, so it is hard to see the precise reason for the failures.
For example, if we look at the confest.py snippet you included, it will definitely fail, but with a different exception - NameError because it will not be able to resolve the names CRYPT_KEY_EDK or CRYPT_KEY values.
Secondly the error message talks about a file test_can_access_netbox.py for which we cannot see here.
This being said, here is a simple example to show how one can use a fixture to set an environment variable:
constants.py
CRYPT_KEY_EDK = "CRYPT-KEY-EDK-VALUE"
CRYPT_KEY = "SOME-CRYPT-KEY-VALUE"
test_fixture_executed.py
import pytest
import os
import constants
#pytest.mark.asyncio
async def test_env_var_is_present():
assert constants.CRYPT_KEY_EDK in os.environ
conftest.py
import pytest
import os
import constants
#pytest.fixture(autouse=True, scope="session")
def set_crypt_key():
print("Entered set_crypt_key fixture")
os.environ[CRYPT_KEY_EDK] = CRYPT_KEY
Okay, i figured it out (thanks to willcode.io asking for the test file):
In my test file i had some lines out of any function. The problem was: they were being executed when pytest was trying to fetch all the tests. But these lines needed to be executed after the fixtures.
Solution: As these floating lines were doing some setup process, i just inserted them into a fixture and everything is working now.
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
)
Tying to set up a simple test suite, I have src/main.py and src/tests.py. I am getting this error.
File "main.py", line 26
def error(msg: str) -> int:
^
SyntaxError: invalid syntax
My code is
from main import error
from typing import *
import unittest
class TestMainFunctions(unittest.TestCase):
def test_error():
result = error("e")
self.assertEqual(result, 0)
unittest.main()
Is there any way to fix this? The error() function works fine when running main.py. Is the unittest module just not compatible with typing? If so, how do I get around this?
I am using pytest fixtures to give a custom object to my tests. I want a fresh object for each test since some of the tests modify the object.
from gen3release.filesys import io
import pytest
from gen3release.config import env
import json
from ruamel.yaml import YAML
import hashlib
import os
import copy
#pytest.fixture(scope="function")
def env_obj():
return env.Env("./data/test_environment.$$&")
The default scope is function which means that the object should give me a new object each for each test.
I have 5 tests for a module called io.py
When I run this test
def test_store_environment_params(env_obj, loaded_env_obj):
io.store_environment_params("./data/test_environment.$$&", env_obj,"manifest.json")
sowers = env_obj.sower_jobs
expected_sower = loaded_env_obj.sower_jobs
assert expected_sower == sowers
env_params = env_obj.ENVIRONMENT_SPECIFIC_PARAMS
expected_params = loaded_env_obj.ENVIRONMENT_SPECIFIC_PARAMS
assert expected_params["manifest.json"] == env_params["manifest.json"], f"Got: {env_params}"
io.store_environment_params("./data/test_environment.$$&/manifests/hatchery/", env_obj,"hatchery.json")
assert expected_params["hatchery.json"] == env_params["hatchery.json"]
Before this test
def test_merge_json_file_with_stored_environment_params(env_obj, loaded_env_obj):
os.system("cp ./data/test_manifest.json ./data/manifest.json")
# h1, j1 = io.read_manifest("./data/manifest.json")
env_params = env_obj.ENVIRONMENT_SPECIFIC_PARAMS["manifest.json"]
print(env_params)
io.merge_json_file_with_stored_environment_params("./data", "manifest.json", env_params, env_obj, loaded_env_obj)
with open("./data/manifest.json") as f:
with open("./data/testmerge_manifest.json") as f2:
assert f2.read() == f.read()
os.system("rm ./data/manifest.json")
This test fails because the env_obj the test receives has been modified. How do I get pytest to give me a new object every time?
I tried to test my code using unittest, where a variable is stored somewhere in a file and accessed using os.getenv
settings.py
import os
TARGET_VAR = os.getenv('TARGET_VAR_VALUE')
some apiclass
from settings import TARGET_VAR
class someApi:
def __init__(self):
print(TARGET_VAR) // good if running normally
....rest of func
test
def test_extreme_devices(self):
app = Flask(__name__)
app.config["LOGIN_DISABLED"] = True
api = flask_restful.Api(app)
api.add_resource('someApi', '/devices/')
with app.test_client() as client:
res = client.get('/devices/')
self.assertEqual(res.status_code, 200)
.env
TARGET_VAR_VALUE='some target value'
This is running good but not when running test
I am confused when running test for testing the someApi class, I got the error
TypeError: int() argument must be a string, a bytes-like object or a number, not
'NoneType'
obviously for the TARGET_VAR is NoneType, even if its value is set in .env
It will work if I add the actual value directly to it
TARGET_VAR = 'actual value'
This issue is only on unittest, but not when flask is running and accessing the route(like postman)
It could be that when you are running the flask app locally the environment already has TARGET_VAR_VALUE loading into the os environment.
If you use something like python-dotenv when running your unittests, you can ensure there's a consistent environment. Just by specifying the correct .env file to use and ensuring it overrides any other env vars:
from dotenv import load_dotenv
load_dotenv("tests/test.env", override=True)
tests/test.env
TARGET_VAR_VALUE='some target value'