Still don't get how to mock an imported library - Python - python

I've looked around but still don't get how to mock a library used inside a function and assert that its been called properly.
a.py
import win32clipboard
def copy():
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText('dummy')
win32clipboard.CloseClipboard()
test_a.py
import a
import pytest
def test_copy():
# Mock win32clipboard somehow
# Run a.copy()
# assert mock win32clipboard.call_count == 4

There is a mistake in your approach.
win32clipboard is a library, with some classes and methods. You must mock every class from this library you want to use (OpenClipboard, EmptyClipboard, SetClipboardText and CloseClipboard)
import a
import pytest
from unittest.mock import patch
#patch('win32clipboard.OpenClipboard')
#patch('win32clipboard.EmptyClipboard')
#patch('win32clipboard.SetClipboardText')
#patch('win32clipboard.CloseClipboard')
def test_copy(mock_close, mock_set, mock_empty, mock_open):
a.copy()
assert mock_close.called
assert mock_set.called
assert mock_empty.called
assert mock_open.called

Related

Check if pytest fixture is called once during testing

Does pytest provides functionality like unittest.mock to check if the mock was actually called once(or once with some parameter)?
Sample Source code:
my_package/my_module.py
from com.abc.validation import Validation
class MyModule:
def __init__(self):
pass
def will_call_other_package(self):
val = Validation()
val.do()
def run(self):
self.will_call_other_package()
Sample test code for the above source code:
test_my_module.py
import pytest
from pytest_mock import mocker
from my_package.my_module import MyModule
#pytest.fixture
def mock_will_call_other_package(mocker):
mocker.patch('my_package.my_module.will_call_other_package')
#pytest.mark.usefixtures("mock_will_call_other_package")
class TestMyModule:
def test_run(self):
MyModule().run()
#check `will_call_other_package` method is called.
#Looking for something similar to what unittest.mock provide
#mock_will_call_other_package.called_once
If you want to use a fixture that does the patching, you can move the patching into a fixture:
import pytest
from unittest import mock
from my_package.my_module import MyModule
#pytest.fixture
def mock_will_call_other_package():
with mock.patch('my_package.my_module.will_call_other_package') as mocked:
yield mocked
# the mocking will be reverted here, e.g. after the test
class TestMyModule:
def test_run(self, mock_will_call_other_package):
MyModule().run()
mock_will_call_other_package.assert_called_once()
Note that you have to use the fixture parameter in the test. Just using #pytest.mark.usefixtures will not give you access to the mock itself. You can still use it to be effective in all tests in the class, if you don't need to access the mock in all tests (or use autouse=True in the fixture).
Also note that you don't need pytest-mock here - but as mentioned by #hoefling, using it makes the fixture better readable, because you don't need the with clause :
#pytest.fixture
def mock_will_call_other_package(mocker):
yield mocker.patch('my_package.my_module.will_call_other_package')
As an aside: you don't need to import mocker. Fixtures are looked up by name, and available automatically if the respective plugin is installed.
You could try this:
import pytest
from my_package.my_module import MyModule
def test_run(mocker):
mocker.patch('my_package.my_module.will_call_other_package')
MyModule().run()
mock_will_call_other_package.assert_called_once()
First of all, you may not need the burden of an external library such as pytest_mock, because pytest already got you covered using the integration with unittest.
You also do not need to use the usefixtures because whenever you need a fixture, you just receive it in your test method.
An ideal scenario based on your own code would look similar to this:
import pytest
from unittest.mock import patch
from com.abc.validation import Validation
class MyModule:
def __init__(self):
pass
def will_call_other_package(self):
val = Validation()
val.do()
def run(self):
self.will_call_other_package()
#pytest.fixture
def call_other_module():
with patch("my_package.my_module.MyModule.will_call_other_package") as _patched:
yield _patched
class TestMyModule:
def test_run_will_call_other_package(self, call_other_module):
call_other_module.assert_not_called()
obj = MyModule()
call_other_module.assert_not_called()
obj.run()
call_other_module.assert_called_once()
And also if you want to make sure that you did infact patch the target MyModule.will_call_other_package, modify your test like this:
class TestMyModule:
def test_run_will_call_other_package(self, call_other_module):
call_other_module.assert_not_called()
obj = MyModule()
call_other_module.assert_not_called()
obj.run()
call_other_module.assert_called_once()
assert False, (MyModule.will_call_other_package, call_other_module)
And you'll see something similar to this:
AssertionError: (<MagicMock name='will_call_other_package' id='140695551841328'>, <MagicMock name='will_call_other_package' id='140695551841328'>)
As you can see the id of both objects are the same, confirming our experiment was successful.

Why is mocking out zeep.Client not working?

I'm writing unit tests for a piece of code that uses zeep to access a SOAP API so I want to mock out zeep. In my actual code, it looks something like this:
from zeep import Client
def do_something():
client = Client("...")
In my test, I'm doing this:
from unittest import mock
#mock.patch('zeep.Client')
def test_do_somethi(self, MockedClient):
do_something()
The Client that the actual function is obtaining, is the actual zeep client and not my mock. I also tried:
#mock.patch('zeep.client.Client')
and the result was the same.
I also tried:
def test_do_something(self):
with mock.patch('zeep.client.Client') as MockedClient:
do_something()
with no difference.
Any ideas why this isn't working?
When mock doesn't work, the first thing to look for is if you are patching the right name. Three common import scenarios:
(a) If you want to mock out zeep but you import like
from zeep import Client
and your tests are in the same file, you patch Client not zeep.Client.
(b) If instead you import it like
import zeep
and then use zeep.Client in SUT code, then you patch zeep.Client.
(c) If you are testing code that lies in some other module (like mymodule) and you import zeep there with
from zeep import Client # (1)
then in your test module you
import mymodule
then you patch mymodule.Client, ... or mymodule.zeep.Client if you used the alternative import zeep form in (1).
You must patch the method/class from file you are using it. If you want to patch Client and it is imported in some_file.py you must import it from it, and not from lib (zeep.Client)
Here is an example using the official doc from Zeep.
some_lib.py
from zeep import Client
def do_something():
wsdl = 'http://www.soapclient.com/xml/soapresponder.wsdl'
client = zeep.Client(wsdl=wsdl)
return client.service.Method1('Zeep', 'is cool')
test_connection.py
from some_lib import do_something
from unittest.mock import patch
#patch('some_lib.Client')
def test_do_something(mock_zeep):
res = do_something()
assert mock_zeep.call_count == 1
if __name__ == '__main__':
test_soap_conn()

How to ignore certain Python modules on import?

I am trying to perform unit tests using Pytest on some of my code. The tests are being run in a separate Conda environment on a Docker. I would like to test certain functionalities of my code but cannot install all the modules of my code, because of the complexity of the installation of some of these modules and the time it would take to run.
How can I import only certain modules from a file, without needing the other modules installed?
If I try running a test, whilst importing a module from a file, my test fails since it cannot import the other modules.
Below is a mock-up of my file system:
test_file.py
from other_file import afunction
def this_test():
assert afunction(2, 2) == 4
other_file.py
import math
import large_program
def afunction(x,y):
return math.pow(x, y)
def anotherfunc():
return large_program()
If I run Pytest, I will get:
E ImportError: No module named 'large_program'
Quite simply: extract the functions that do not depend on large_program into another module and only test this module. Note that you can do this without breaking client code (code depending on your other_file module) by importing the relevant names in other_file:
# utils.py
import math
def afunction(x,y):
return math.pow(x, y)
then
# other_file.py
import large_program
# this will allow client code to access `afunction` from `other_file`
from utils import afunction
def anotherfunc():
return large_program()
and finally:
# test_file.py
# here we import from utils so we don't depend on `large_program`
from utils import afunction
def this_test():
assert afunction(2, 2) == 4
I liked the idea of cristid9 of mocking and combined it with dano's post here. I created an empty file called "nothing.py" that will replace the "large_program" with a unittest mock:
test_file.py
import unittest.mock as mock
import nothing
with mock.patch.dict('sys.modules', large_program=nothing):
from other_file import afunction
def this_test():
assert afunction(2, 2) == 4
The other_file.py looks still like this
import math
import large_program
def afunction(x,y):
return math.pow(x, y)
def anotherfunc():
return large_program()
You can also apply the with statement on multiple modules:
other_file.py
import math
import large_program
import even_larger_program
def afunction(x,y):
return math.pow(x, y)
def anotherfunc():
return large_program()
test_file.py
import unittest.mock as mock
import nothing
with mock.patch.dict('sys.modules', large_program=nothing), mock.patch.dict('sys.modules', even_larger_program=nothing):
from other_file import afunction
def this_test():
assert afunction(2, 2) == 4

How to mock variable in included library

Folks,
I have a problem during including file.py to test_file.py namely:
file.py uses Robot library BuiltIn:
from robot.libraries.BuiltIn import BuiltIn
DEFAULT_IPHY_TTI_TRACE_DIR =
os.path.join(BuiltIn().get_variable_value('${OUTPUT_DIR}'), 'iphy_tti_trace')
And when I try to include file.py in my test_file.py
import pytest
#import file.py
I receive:
test_file.py:8: in <module>
/opt/ute/python/lib/python2.7/site-packages/robot/libraries/BuiltIn.py:1331: in get_variable_value
return self._variables[self._get_var_name(name)]
/opt/ute/python/lib/python2.7/site-packages/robot/libraries/BuiltIn.py:75: in _variables
return self._namespace.variables
/opt/ute/python/lib/python2.7/site-packages/robot/libraries/BuiltIn.py:71: in _namespace
return self._get_context().namespace
/opt/ute/python/lib/python2.7/site-packages/robot/libraries/BuiltIn.py:66: in _get_context
raise RobotNotRunningError('Cannot access execution context')
E RobotNotRunningError: Cannot access execution context
How can I mock this? This is posible at all?
Sure, the issue is just that you can't mock the BuiltIn class where it is used (in file.py). You have to mock the class where it is declared (in robot.libraries.BuiltIn).
Using mocks:
from unittest.mock import patch, MagicMock
def _test_default_iphy_tti_trace_dir():
with patch('robot.libraries.BuiltIn.BuiltIn.get_variable_value', return_value='/foo/bar'):
import file
assert file.DEFAULT_IPHY_TTI_TRACE_DIR == '/foo/bar/iphy_tti_trace'
Using monkeypatch fixture:
def test_default_iphy_tti_trace_dir(monkeypatch):
def mocked_get(self, name):
return '/foo/bar'
monkeypatch.setattr('robot.libraries.BuiltIn.BuiltIn.get_variable_value', mocked_get)
import file
assert file.DEFAULT_IPHY_TTI_TRACE_DIR == '/foo/bar/iphy_tti_trace'
Also note that the mocking is done for the scope of a single test only, so you can't import file on top of the test module as the BuiltIn will be unpatched there, raising the context error.

Python: issue with building mock function

I'm writing unit tests to validate my project functionalities. I need to replace some of the functions with mock function and I thought to use the Python mock library. The implementation I used doesn't seem to work properly though and I don't understand where I'm doing wrong. Here a simplified scenario:
root/connector.py
from ftp_utils.py import *
def main():
config = yaml.safe_load("vendor_sftp.yaml")
downloaded_files = []
downloaded_files = get_files(config)
for f in downloaded_files:
#do something
root/utils/ftp_utils.py
import os
import sys
import pysftp
def get_files(config):
sftp = pysftp.Connection(config['host'], username=config['username'])
sftp.chdir(config['remote_dir'])
down_files = sftp.listdir()
if down_files is not None:
for f in down_files:
sftp.get(f, os.path.join(config['local_dir'], f), preserve_mtime=True)
return down_files
root/tests/connector_tester.py
import unittest
import mock
import ftp_utils
import connector
def get_mock_files():
return ['digital_spend.csv', 'tv_spend.csv']
class ConnectorTester(unittest.TestCase)
#mock.patch('ftp_utils.get_files', side_effect=get_mock_files)
def test_main_process(self, get_mock_files_function):
# I want to use a mock version of the get_files function
connector.main()
When I debug my test I expect that the get_files function called inside the main of connector.py is the get_mock_files(), but instead is the ftp_utils.get_files(). What am I doing wrong here? What should I change in my code to properly call the get_mock_file() mock?
Thanks,
Alessio
I think there are several problems with your scenario:
connector.py cannot import from ftp_utils.py that way
nor can connector_tester.py
as a habit, it is better to have your testing files under the form test_xxx.py
to use unittest with patching, see this example
In general, try to provide working minimal examples so that it is easier for everyone to run your code.
I modified rather heavily your example to make it work, but basically, the problem is that you patch 'ftp_utils.get_files' while it is not the reference that is actually called inside connector.main() but probably rather 'connector.get_files'.
Here is the modified example's directory:
test_connector.py
ftp_utils.py
connector.py
test_connector.py:
import unittest
import sys
import mock
import connector
def get_mock_files(*args, **kwargs):
return ['digital_spend.csv', 'tv_spend.csv']
class ConnectorTester(unittest.TestCase):
def setUp(self):
self.patcher = mock.patch('connector.get_files', side_effect=get_mock_files)
self.patcher.start()
def test_main_process(self):
# I want to use a mock version of the get_files function
connector.main()
suite = unittest.TestLoader().loadTestsFromTestCase(ConnectorTester)
if __name__ == "__main__":
unittest.main()
NB: what is called when running connector.main() is 'connector.get_files'
connector.py:
from ftp_utils import *
def main():
config = None
downloaded_files = []
downloaded_files = get_files(config)
for f in downloaded_files:
print(f)
connector/ftp_utils.py unchanged.

Categories