Pytest path of mock.patch() for third party package - python

Why the 1st case succeed. But the 2nd case failed AssertionError: Expected 'Jenkins' to have been called.
util.py
from jenkinsapi.jenkins import Jenkins
import os
class Util:
#staticmethod
def rm(filename):
os.remove(filename)
#staticmethod
def get_jenkins_instance():
Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_remove(mocker):
m = mocker.patch('os.remove')
Util.rm('file')
m.assert_called()
def test_util_get_instance(mocker):
m = mocker.patch('jenkinsapi.jenkins.Jenkins')
Util.get_jenkins_instance()
m.assert_called()
Two files are in the same root folder.

Not very clear what's the differences between Python's import and from ... import ....
But if you use from ... import ..., the mock looks as following:
util.py
from jenkinsapi.jenkins import Jenkins # <-- difference A
class Util:
#staticmethod
def get_jenkins_instance():
Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_get_instance(mocker):
m = mocker.patch('util.Jenkins') # <-- difference B
Util.get_jenkins_instance()
m.assert_called()
If you use import directly, the mock looks as following:
util.py
import jenkinsapi.jenkins # <-- difference A
class Util:
#staticmethod
def get_jenkins_instance():
jenkinsapi.jenkins.Jenkins(
'host',
username='username',
password='password',
ssl_verify=False,
lazy=True)
test_util.py
import pytest
from util import Util
def test_util_get_instance(mocker):
m = mocker.patch('jenkinsapi.jenkins.Jenkins') # <-- difference B
Util.get_jenkins_instance()
m.assert_called()
========== Edit (Aug 5, 2022) ==========
Here's the reason why patched like this.
a.py
-> Defines SomeClass
b.py
-> from a import SomeClass
-> some_function instantiates SomeClass
Now we want to test some_function but we want to mock out SomeClass using patch(). The problem is that when we import module b, which we will have to do then it imports SomeClass from module a. If we use patch() to mock out a.SomeClass then it will have no effect on our test; module b already has a reference to the real SomeClass and it looks like our patching had no effect.
The key is to patch out SomeClass where it is used (or where it is looked up ). In this case some_function will actually look up SomeClass in module b, where we have imported it.

Related

unittest mock cannot find attribute

I'm getting the following error:
AttributeError: <class 'workflow.workflow.Task'> does not have the attribute 'extract'
This is how the codes are arranged
src
|_ workflow
|_ workflow.py
|_ tests
|_ test_extract.py
|_ data_extractor:
|_ data_extractor.py
This is workflow.py:
from data_extractor.data_extractor import DataExtractor
class Task:
def __init__(self) -> None:
self.extractor = DataExtractor()
def extract_data(self):
obj = self.extractor.extract()
In test_extract.py:
from unittest import mock, TestCase
from workflow.workflow import Task
class TestSomeExtract(TestCase):
#mock.patch("workflow.workflow.Task.extract")
def test_extract_from_snowflake(self, mock_extract):
actual_result = Task.extract_data()
self.assertTrue(actual_result)
if __name__ == "__main__":
TestCase.main()
I think I did it right but...
UPDATE 24/6:
In test_extract.py:
import unittest
from unittest import mock
from workflow.workflow import DataExtractor
#mock.patch("workflow.workflow.DataExtractor")
class TestSomeExtract(unittest.TestCase):
def test_extract_from_snowflake(self, mock_extract):
mock_extract.return_value.extract.return_value = True
actual_result = DataExtractor().extract(name="entities", key="11") # return a list
self.assertTrue(actual_result)
mock_extract.assert_called_once_with(actual_result)
if __name__ == "__main__":
unittest.main()
In workflow.py:
from data_extractor.data_extractor import DataExtractor
class Task:
def __init__(self, type: str, name: str) -> None:
self.name = name
self.type = type
self.extractor = DataExtractor()
def extract_data(self):
obj = self.extractor.extract(name=self.name, key=key)
Not much of difference besides I added assert_called_once_with in the test case.
Here is a working code example, based in your updated version:
import unittest
from unittest import mock
#mock.patch("workflow.workflow.DataExtractor")
class TestSnowFlakeExtract(unittest.TestCase):
def test_extract_from_snowflake(self, mock_extract):
from workflow.workflow import DataExtractor
mock_extract.return_value.extract.return_value = True
actual_result = DataExtractor().extract(name="stuff", key="11")
self.assertTrue(actual_result)
Some things to highlight:
When we mock something, we replace the attribute. So #mock.patch("workflow.workflow.DataExtractor") replaces the DataExtractor attribute inside workflow.workflow package with a mock object - it doesn't affect the data_extractor.data_extractor package, so we shouldn't use the data_extractor.data_extractor package directly.
Emphasising the previous point: #mock.patch("workflow.workflow.DataExtractor") is translated into these two statements:
import workflow.workflow as module_to_be_patched
module_to_be_patched.DataExtractor = MagicMock()
The mock/patch happens at the beginning of our test, so if the following import statement had been global: from workflow.workflow import DataExtractor, we wouldn't use the mocked version, since we first name DataExtractor inside our test module to be the original data_extractor.data_extractor object and only then execute the patch statement, which only affects the workflow module.
You need to be exact on the mock return syntax: you are calling DataExtractor() which is equivalent to mock_extract.return_value and then you chain the .extract(name="stuff", key="11") which is equivalent to .extract.return_value. Hence the full syntax should be mock_extract.return_value.extract.return_value = True
In actual testing we wouldn't be importing DataExtractor from workflow.workflow, but rather import workflow.workflow and call its functionality, relying on the fact that DataExtractor was replaced by our test.
UPDATE 24/6:
Here is an updated test:
import unittest
from unittest import mock
from workflow.workflow import Task
#mock.patch("workflow.workflow.DataExtractor")
class TestSomeExtract(unittest.TestCase):
def test_extract_from_snowflake(self, mock_extract):
mock_extract.return_value.extract.return_value = True
actual_result = Task(name="entities", key="11").extract_data()
self.assertTrue(actual_result)
mock_extract.assert_called_once_with() # mock_extract is equal to DataExtractor(), which is called without parameters
A couple of things to notice:
We Test the workflow module, hence we call the initialization of Task and then call extract_data.
We only test workflow module, so we mock other dependencies like the DataExtractor of that module.
I had to do minor changes to Task class. For example, change it to get the key parameter from outside.

How to patch same class in multiple tests?

I want to patch a class in a Python library and mock its methods independently in several tests. My approach works if I run the tests separately. However, if I run the tests using $ python -m unittest test_*.py the second test appears to use the patched object from the first and fails.
Here's a simplified version of my setup. (In the real scenario I have a more complex dependency on the mocked class and multiple tests in each test class using different mock functions as side effect.)
Files:
lib.py
test_1.py
test_2.py
lib.py:
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
client = BackendApplicationClient(client_id='a client')
session = OAuth2Session(client=client)
def get_data():
return session.get('https://some.url.com/data')
test_1.py:
import unittest
from unittest.mock import patch
with patch('requests_oauthlib.OAuth2Session') as MockSession:
mock_session = MockSession.return_value
from .lib import get_data
def mock_get(url):
return f'some data from {url}'
class Test(unittest.TestCase):
def test_get_data(self):
mock_session.get.side_effect = mock_get
self.assertEqual(get_data(), 'some data from https://some.url.com/data')
test_2.py:
import unittest
from unittest.mock import patch
with patch('requests_oauthlib.OAuth2Session') as MockSession:
mock_session = MockSession.return_value
from .lib import get_data
def mock_get(url):
return f'other data from {url}'
class Test(unittest.TestCase):
def test_get_data(self):
mock_session.get.side_effect = mock_get
self.assertEqual(get_data(), 'other data from https://some.url.com/data')
When doing
$ python -m unittest test_1.py
$ python -m unittest test_2.py
both tests yield OK.
Doing
$ python -m unittest test_*.py
fails with
AssertionError: 'some data from https://some.url.com/data' != 'other data from https://some.url.com/data'
I also tried to use patch() like so:
patcher = patch('requests_oauthlib.OAuth2Session')
MockSession = patcher.start()
mock_session = MockSession()
from .lib import get_data
and introduce explicit module teardown:
def tearDownModule():
patcher.stop()
but got the same results.
Finally, I tried moving the patch() into a class setup:
import unittest
from unittest.mock import patch
my_get_data = None
def mock_get(url):
return f'some data from {url}'
class Test(unittest.TestCase):
patcher = None
mock_session = None
#classmethod
def setUpClass(cls):
global my_get_data
cls.patcher = patch('requests_oauthlib.OAuth2Session')
MockSession = cls.patcher.start()
cls.mock_session = MockSession()
from python3.test.operators.com.sap.mdi.consumer.v2.tmp_c.lib import get_data
my_get_data = get_data
#classmethod
def tearDownClass(cls):
cls.patcher.stop()
def test_get_data(self):
self.mock_session.get.side_effect = mock_get
self.assertEqual(my_get_data(), 'some data from https://some.url.com/data')
but still got the same AssertionError.
What am I doing wrong?

How to properly mock a function that instantiates a class variable?

I have something like this in my source file
# code.py
def some_func():
# doing some connections and stuff
return {'someKey': 'someVal'}
class ClassToTest:
var = some_func()
My test file looks like this... I am trying to mock some_func as I want to avoid creating connections.
# test_code.py
from src.code import ClassToTest
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
#mock.patch('src.code.some_func', new=mock_function)
def test_ClassToTest(self):
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})
But this doesn't work. On the other hand if var is an instant variable mock works fine. I guess this is due to class variables getting initialized during imports. How do I properly mock some_func before var even gets initialized?
When you imported code.py, there is no active patch yet, so when ClassToTest.var was initialized, it used the original some_func. Only then would the patch for src.code.some_func would take effect which obviously is too late now.
Solution 1
What you can do is to patch some_func and then reload code.py so that it re-initializes the ClassToTest including its attribute var. Thus since we already have an active patch by the time we reload code.py, then ClassToTest.var would be set with the patched value.
But we can't do it if both the class and the patched function lives in the same file, so to make it testable move some_func to another file and then just import it.
src/code.py
from src.other import some_func
class ClassToTest:
var = some_func()
src/other.py
def some_func():
# doing some connections and stuff
return {'realKey': 'realValue'}
test_code.py
from importlib import reload
import sys
import unittest
from unittest import mock
from src.code import ClassToTest # This will always refer to the unpatched version
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
def test_real_first(self):
self.assertEqual(ClassToTest.var, {"realKey": "realValue"})
#mock.patch('src.other.some_func', new=mock_function)
def test_mock_then_reload(self):
# Option 1:
# import src
# reload(src.code)
# Option 2
reload(sys.modules['src.code'])
from src.code import ClassToTest # This will be the patched version
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})
def test_real_last(self):
self.assertEqual(ClassToTest.var, {"realKey": "realValue"})
Output
$ pytest -q
... [100%]
3 passed in 0.04s
Solution 2
If you don't want the real some_func to be ever called during test, then just reloading isn't enough. What needs to be done is to never import the file containing ClassToTest nor any file that would import it indirectly. Only import it once an active patch for some_func is already established.
from importlib import reload
import sys
import unittest
from unittest import mock
# from src.code import ClassToTest # Remove this import!!!
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
#mock.patch('src.other.some_func', new=mock_function)
def test_mock_then_reload(self):
from src.code import ClassToTest # Move the import here once the patch has taken effect already
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})

Python mock: How to mock a class being called from the class method being tested

Let's say I have the below code:
bm.py
from a.b import AC
class B:
def __init__(self, **kwargs):
self.blah = kwargs["blah"]
def foo(self, message):
# Do something here and then finally use AC().send()
AC().send(message)
I am writing the below test case for the above:
import pytest
import unittest
from mock import patch
from a.b import AC
import B
class TestB(unittest.TestCase):
#patch('AC.send')
def test_foo(self, mock_send):
s = B(blah="base")
s.foo("Hi!")
## What to assert here???
I would like to mock the AC.send. AC.send doesn't return anything because it is "sending" to some external service/machine. And also, B.foo() doesn't return anything either. So I'm not sure what I should assert and check?
With the above test case I get the below error:
ModuleNotFoundError: No module named 'AC'
I'm new to Unit test cases and mocking.
regarding
ModuleNotFoundError: No module named 'AC'
You should use the full quilifired name in #patch - in your case #patch('a.b.AC.send')
regarding
## What to assert here???
This question is too broad and application dependent. Generally, you need to ask yourself what is expected from the production code.
Sometimes you only want to check that there are no exceptions. In this case you don't need to assert anything. If there will be an exception, the test will fail.
Suggest to read this fine post this
You can use the full import.
#patch('a.b.AC.send')
Based on what #Florian Bernard mentioned in the comment section, the below works!
import pytest
import unittest
from mock import patch
from a.b import AC
import B
class TestB(unittest.TestCase):
#patch('a.b.AC.send') ##Provide the full import
def test_foo(self, mock_send):
s = B(blah="base")
s.foo("Hi!")

Python: Mocking a module that's being used by a module I'm testing

I would like to mock a certain module in order to test a piece of code that is using the module.
That is to say, I have a module my_module which I'd like to test. my_module imports an external module real_thing and calls real_thing.compute_something():
#my_module
import real_thing
def my_function():
return real_thing.compute_something()
I need to mock real_thing so that in a test it will behave like fake_thing, a module that I've created:
#fake_thing
def compute_something():
return fake_value
The test calls my_module.my_function() which calls real_thing.compute_something():
#test_my_module
import my_module
def test_my_function():
assert_something(my_module.my_function())
What should I add to the test code so that my_function() will call fake_thing.compute_something() within the test instead of real_thing.compute_something()?
I was trying to figure out how to do so with Mock, but I haven't.
Simply that no ? Hack the sys.modules
#fake_thing.py
def compute_something():
return 'fake_value'
#real_thing.py
def compute_something():
return 'real_value'
#my_module.py
import real_thing
def my_function():
return real_thing.compute_something()
#test_my_module.py
import sys
def test_my_function():
import fake_thing
sys.modules['real_thing'] = fake_thing
import my_module
print my_module.my_function()
test_my_function()
Outputs : 'fake_value'
http://code.google.com/p/mockito-python/
>>> from mockito import *
>>> dog = mock()
>>> when(dog).bark().thenReturn("wuff")
>>> dog.bark()
'wuff'
http://technogeek.org/python-module.html - how to replace ,load module dynamically

Categories

Resources