Mocking environment variables with starlette config - python

from starlette.config import Config
config = Config(".env")
SOME_ENV_VAR: str = config("SOME_ENV_VAR", cast=str, default="abc")
I'm looking for a way to mock the value of SOME_ENV_VAR for unit tests. Is there such an option?
# mock config somehow so that config.SOME_ENV_VAR = "xyz"
def some_test():
assert config.SOME_ENV_VAR == "xyz"

You can use Python's unittest.mock.patch method:
from unittest.mock import patch
from your_config_module import config
#patch('config.SOME_ENV_VAR', 'xyz')
def some_test():
assert config.SOME_ENV_VAR == "xyz"

Related

Unittest mock: return function which receives one pytest fixture and one argument

I'm creating some unit tests and using pytest fixtures in combination with some unittest.mock patch.object calls.
I would like to reuse a function that is called by some of my tests. It makes use of a pytest fixture (specified as the first "argument" of the function) and it requires an additional argument. It looks something like this:
import pandas as pd
import pytest
import os
from unittest.mock import patch
#pytest.fixture()
def rootdir():
return os.path.dirname(os.path.abspath(__file__))
def my_mock_ret(rootdir, number):
print(f"{rootdir}_{number}")
return f"{rootdir}_{number}"
def test_simple(rootdir):
a = pd.DataFrame()
with patch.object(a, 'to_csv', lambda x: my_mock_ret(rootdir, x)) as _:
a.to_csv(rootdir)
The tricky part is how to pass the number argument to my_mock_ret while also being able to access the rootdir fixture from inside it.
I've tried this way using lambda but it does not work.
Edit
It works if y put my_mock_ret inside test_simple, but I don't want to do that because I want to reuse my_mock_ret for several other tests:
import pandas as pd
import pytest
import os
from unittest.mock import patch
#pytest.fixture()
def rootdir():
return os.path.dirname(os.path.abspath(__file__))
def test_simple(rootdir):
def my_mock_ret(number):
print(f"{rootdir}_{number}")
return f"{rootdir}_{number}"
a = pd.DataFrame()
with patch.object(a, 'to_csv', my_mock_ret) as _:
a.to_csv(rootdir)
What you need right here, is the factory pattern:
import pandas as pd
import pytest
import os
from unittest.mock import patch
#pytest.fixture()
def rootdir():
return os.path.dirname(os.path.abspath(__file__))
#pytest.fixture()
def my_mock_ret(rootdir):
def _my_mock_ret(number):
print(f"{rootdir}_{number}")
return f"{rootdir}_{number}"
return _my_mock_ret
def test_simple(my_mock_ret, rootdir):
a = pd.DataFrame()
with patch.object(a, 'to_csv', my_mock_ret) as _:
a.to_csv(rootdir)
here my_mock_ret will create a function, which captures rootdir, and can take the number argument.

Mockito: mock a subscriptable class

I would like to mock ConfigParser class with Mockito to perform a simple test:
from configparser import ConfigParser
from unittest import TestCase
from mockito import when, mock, verify
section = "SETTINGS"
# Here is an example function to test
def get_config():
ini_file = "settings.ini"
parser = ConfigParser()
parser.read(ini_file)
settings = parser[section]
db_name = settings['db_name']
db_type = settings['db_type']
return (db_name, db_type)
class MyTest(TestCase):
def test_0_check_get_config(self):
parser = mock(ConfigParser)
expected_db_name = 'test.sqlite'
expected_db_type = 'sqlite'
settings = {
'db_name': expected_db_name,
'db_type': expected_db_type
}
# The line above will fail: TypeError: 'Dummy' object is not subscriptable
# I need to make the mocked class to return a 'settings' dictionary when 'SETTINGS' key is requested.
when(parser[section]).thenReturn(settings)
db_name, db_type = get_config()
assert db_name == 'test.sqlite'
assert db_type == 'sqlite'
The code is very self-explanatory. I would like the mocked class to return a corresponding dictionary when a particular key is requested.
There are a plenty of similar questions regarding 'regular' python unittest module. However, I would like to find an answer for Mockito mocking.
You need to stub ("when") the special dunder functions. E.g.
In [1]: from mockito import *
In [2]: m = mock()
In [3]: when(m).__getitem__("hi").thenReturn("ho")
Out[3]: <mockito.invocation.AnswerSelector at 0x1da3ba1d300>
In [4]: m["hi"]
Out[4]: 'ho'
This solves the "subscriptable" problem but keep in mind that the exact example you gave uses a global ConfigParser (t.i. you don't dependency inject here). Just calling mock(ConfigParser) will not monkey-patch the global object. Instead it creates a new object ("instance") you can pass around ("inject").

Why is my constant mock with python unit test not working

I use some mocking code to mock a constant in python. I read the docs over and over again but I don't see what I do wrong:
from unittest import TestCase, mock
from sync_servicenowjira.adapter_sn_to_jira import adapt_sn_to_jira
from sync_servicenowjira.constants import DOWNLOAD_PATH
from sync_servicenowjira.dao_service_now import get_service_now_tickets
# #mock.patch('sync_servicenowjira.constants.DOWNLOAD_PATH', './test_files/')
class Test(TestCase):
def test_adapt_sn_to_jira(self):
with mock.patch('sync_servicenowjira.constants.DOWNLOAD_PATH', './test_files/'):
test = DOWNLOAD_PATH
print(test)
sn_tickets = get_service_now_tickets(True)
jira_tickets = adapt_sn_to_jira(sn_tickets)
self.assertIsInstance(jira_tickets, list)
self.assertGreater(len(jira_tickets), 1)

Pytest - mocking a side_effect on mock's nested attribute function / method

I have a fixture mocking an external library like so, using pytest-mock, which is a wrapper around unittest.mock.
# client.py
import Test as TestLibrary
class LibraryName():
def get_client():
return TestLibrary.Library()
# library_service.py
def using_library():
'''
Edited note: Library().attribute behind the scenes is set to
self.attribute = Attribute()
so this may be affecting the mocking
'''
client = LibraryName.get_client()
return client.attribute.method()
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
# test_library_service.py
def test_library_method(library_client_mock):
result = using_library()
I can mock a return value like so:
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
result = using_library()
assert result == "test"
but I can't mock throwing an Exception with side_effect
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError # doesn't work
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError() # doesn't work
attrs = { 'attribute.method.side_effect': TypeError }
library_client_mock.configure_mock(**attrs) # doesn't work
with pytest.raises(TypeError):
using_library() # fails assertion
what I missing here?
These are the errors in your code:
Change:
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
To:
library_client_mock.return_value.attribute.method.return_value = "test"
Change:
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError
To:
library_client_mock.return_value.attribute.method.side_effect = TypeError
Explanation
The .return_value must only be used for callable objects e.g. a function as documented:
return_value
Set this to configure the value returned by calling the mock:
>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'
Thus, you can use .return_value only for the following:
TestLibrary.Library()
TestLibrary.Library().attribute.method()
But not for:
TestLibrary.Library().attribute
Because .attribute is not a callable e.g. TestLibrary.Library().attribute().
Warning
The way you are patching Library is via its source location at Test.Library (or aliased as TestLibrary.Library). specifically via:
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
It works currently because the way you import and use it is via the root path.
# client.py
import Test as TestLibrary
...
return TestLibrary.Library()
...
But if we change the way we imported that library and imported a local version to client.py:
# client.py
from Test import Library # Instead of <import Test as TestLibrary>
...
return Library() # Instead of <TestLibrary.Library()>
...
It will now fail. Ideally, you should patch the specific name that is used by the system under test, which here is client.Library.
import client
return mocker.patch.object(client, 'Library')
Unless you are sure that all files that will use the library will import only the root and not a local version.
#Niel Godfrey Ponciano set me on the right path with this syntax for the side_effect
library_client_mock.return_value.attribute.method.side_effect = TypeError
but it wasn't enough.
In
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
I had to add an extra mock:
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
mock_library_client = mocker.patch.object(TestLibrary, 'Library')
# option 1
mock_attribute = Mock()
# option 2, path to Library.attribute = Attribute()
mock_attribute = mocker.patch.object(TestLibrary.services, 'Attribute', autospec=True)
mock_library_client.attach_mock(mock_attribute, "attribute")
return mock_library_client
and then both of the following statements worked as expected. Although I am not sure why return_value works out of the box without an attached mock, but side_effect does not.
# return_value set correctly
# NOTE return_value needed after each
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
# side_effect set correctly
# NOTE return_value not needed after "attribute"
library_client_mock.return_value.attribute.method.side_effect = TypeError

Pytest mock: how to patch an import statement inside a function [duplicate]

Is it possible to mock a module in python using unittest.mock? I have a module named config, while running tests I want to mock it by another module test_config. how can I do that ? Thanks.
config.py:
CONF_VAR1 = "VAR1"
CONF_VAR2 = "VAR2"
test_config.py:
CONF_VAR1 = "test_VAR1"
CONF_VAR2 = "test_VAR2"
All other modules read config variables from the config module. While running tests I want them to read config variables from test_config module instead.
If you're always accessing the variables in config.py like this:
import config
...
config.VAR1
You can replace the config module imported by whatever module you're actually trying to test. So, if you're testing a module called foo, and it imports and uses config, you can say:
from mock import patch
import foo
import config_test
....
with patch('foo.config', new=config_test):
foo.whatever()
But this isn't actually replacing the module globally, it's only replacing it within the foo module's namespace. So you would need to patch it everywhere it's imported. It also wouldn't work if foo does this instead of import config:
from config import VAR1
You can also mess with sys.modules to do this:
import config_test
import sys
sys.modules["config"] = config_test
# import modules that uses "import config" here, and they'll actually get config_test
But generally it's not a good idea to mess with sys.modules, and I don't think this case is any different. I would favor all of the other suggestions made over it.
foo.py:
import config
VAR1 = config.CONF_VAR1
def bar():
return VAR1
test.py:
import unittest
import unittest.mock as mock
import test_config
class Test(unittest.TestCase):
def test_one(self):
with mock.patch.dict('sys.modules', config=test_config):
import foo
self.assertEqual(foo.bar(), 'test_VAR1')
As you can see, the patch works even for code executed during import foo.
If you want to mock an entire module just mock the import where the module is used.
myfile.py
import urllib
test_myfile.py
import mock
import unittest
class MyTest(unittest.TestCase):
#mock.patch('myfile.urllib')
def test_thing(self, urllib):
urllib.whatever.return_value = 4
Consider this following setup
configuration.py:
import os
class Config(object):
CONF_VAR1 = "VAR1"
CONF_VAR2 = "VAR2"
class TestConfig(object):
CONF_VAR1 = "test_VAR1"
CONF_VAR2 = "test_VAR2"
if os.getenv("TEST"):
config = TestConfig
else:
config = Config
now everywhere else in your code you can use:
from configuration import config
print config.CONF_VAR1, config.CONF_VAR2
And when you want to mock your coniguration file just set the environment variable "TEST".
Extra credit:
If you have lots of configuration variables that are shared between your testing and non-testing code, then you can derive TestConfig from Config and simply overwrite the variables that need changing:
class Config(object):
CONF_VAR1 = "VAR1"
CONF_VAR2 = "VAR2"
CONF_VAR3 = "VAR3"
class TestConfig(Config):
CONF_VAR2 = "test_VAR2"
# CONF_VAR1, CONF_VAR3 remain unchanged
If your application ("app.py" say) looks like
import config
print config.var1, config.var2
And gives the output:
$ python app.py
VAR1 VAR2
You can use mock.patch to patch the individual config variables:
from mock import patch
with patch('config.var1', 'test_VAR1'):
import app
This results in:
$ python mockimport.py
test_VAR1 VAR2
Though I'm not sure if this is possible at the module level.

Categories