Mock not seen by pytest - python

I got a class that describes an entity and am trying to write unit tests that should check if certain fields are defaulted to the correct value. One of those fields uses datetime.now() to set the default state.
I am having trouble trying to mock this call to now() in my test. I am guessing it has to do with my folder structure.
src
classes
MyClass.py
pytests
test_MyClass.py
The MyClass definition:
from datetime import datetime
class MyClass:
def __init__(self):
self.mom = datetime.now()
I am using #mock.patch as follows (inside test_MyClass.py):
import pytest
import sys
from unittest import mock
sys.path.append(r'C:\python-projects\test\src')
from classes.MyClass import MyClass
#mock.patch('classes.MyClass.datetime.now', return_value = 'timestamp', autospec = True)
#pytest.fixture
def myclass():
myclass = MyClass()
yield myclass
def test_has_datetime(myclass):
assert myclass.mom == 'timestamp'
The test ignores the patch.

Try this:
make sure you have __init__.py files in src, classesand pytests directories;
remove sys.path.append(r'C:\python-projects\test\src')
in #mock.patch, replace classes.MyClass.datetime.now with src.classes.MyClass.datetime.now
make C:\python-projects\test\ the current working directory and run pytest ./pytests
Otherwise, mocking the datetime module can be easier with packages like freezegun: https://github.com/spulec/freezegun

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.

Mock a class before importing it

I'm trying to mock a method in a parent class that is declared out of any class method. The problem es I can't figure out how to make the LoggerUtils class to be instantiated in the parent class. Puting it inside an __init__ is not an option due to the large size of the implementation and the cost of refactor.
Is there a way to mock the parent class prior to the import of the tested class?
At least mock the method before is loaded when importing the class.
Is there any approach to solve the problem of non-lazy methods loading on import?
I tried lazy loading TensorFlow LazyLoad Library methods but I just didnt get it to work; patching all methods with mock library, but the methods always load before I can mock anything. Below I have an example of the mocking tries, but LoggerUtils is always called.
Parent Class:
abstract_api.py
class AbstractApi:
logger = LoggerUtils.get_logger('AbstractApi')
def update(self):
<code>
Class to test:
api_map_asset_type.py
from abstract_api import AbstractApi
class ApiMapAssetType(AbstractApi):
def update(self):
<code>
Test Class:
test_api_map_asset_type.py
from unittest import TestCase
from mock imort patch
from api_map_asset_type import ApiMapAssetType
class TestApiMapAssetType(TestCase):
#patch('api_map_asset_type.AbstractApi.LoggerUtils')
#patch('api_map_asset_type.AbstractApi')
def setUp(self, mock2_abstract_loger, mock_3):
self.asset_api = ApiMapAssetType()
#patch('AbstractApi.update')
def test_update(self, mock_parent_update):
mock_orm = MagicMock()
self.asset_api.update()
mock_parent_update.assert_called_with()
EDITED
This is the only solution I found, since I am not able to mock parent classes or mock methods in class attributes before being imported, I decided to mock the whole test before importing, yet I think this is not an optimal or clean solution:
Test Class:
test_api_map_asset_type.py
from undetermined_project_library.LoggerUtils import LoggerUtils
with patch.object(LoggerUtils, 'get_logger') as mock_logger:
from unittest import TestCase
from mock imort patch
from api_map_asset_type import ApiMapAssetType
class TestApiMapAssetType(TestCase):
def setUp(self):
self.asset_api = ApiMapAssetType()
#patch('AbstractApi.update')
def test_update(self, mock_parent_update):
mock_orm = MagicMock()
self.asset_api.update()
mock_parent_update.assert_called_with()
Provided I understood your module layout, this should work:
class TestApiMapAssetType(TestCase):
#patch('undetermined_project_library.LoggerUtils')
def setUp(self, mock_abstract_logger):
self.asset_api = ApiMapAssetType()
#patch('api_map_asset_type.ApiMapAssetType.update')
def test_update(self, mock_parent_update):
self.asset_api.update()
mock_parent_update.assert_called_with()
Note a couple of things:
you have to mock LoggerUtils from the library they are defined in; you have used AbstractApi.LoggerUtils, which is not correct, as LoggerUtils does not belong to AbstractApi
I removed the mock for AbstractApi - at least for this you don't need it
update is patched for the method that is actually called - using the base class method may make sense if you call the base implementation (don't know if you do)
I guessed some of your module layout - you may have to adapt it
You can do this by using the sys module in python. You can register the imports with sys module and replace the implementation with Mock or MagicMock instances.
This is how it looks like in code -
import sys
from unittest.mock import MagicMock
sys.modules['numpy'] = MagicMock()
sys.modules['pandas'] = MagicMock()
sys.modules['torch'] = MagicMock()
sys.modules['torch.nn'] = MagicMock()
sys.modules['torch.nn.functional'] = MagicMock()
sys.modules['dgl'] = MagicMock()
sys.modules['dgl.ops'] = MagicMock()
sys.modules['dgl.function'] = MagicMock()
from path.to.module_under_test import ClassUnderTest
Assuming module_under_test.py starts like -
import numpy as np
import pandas as pd
import torch
import dgl
import torch.nn.functional as F
from dgl.ops import edge_softmax
class ClassUnderTest:
# code

How to mock a base_class from an external module?

I'm trying to unit test a class which is derived from a base_class in an external module. In my dev/test environment I have not access to this external module, which means I have to somehow mock this base_class.
My test-code resides in a different file from the code I'm trying to test.
The problem can be summarized as follows:
my_class.py
import external_module
class MyClass(external_module.ExternalClass):
def test_method(self):
return "successful"
test_my_class.py
import sys
import unittest
from unittest.mock import MagicMock
sys.modules['external_module'] = MagicMock()
from my_class import MyClass
class TestMyClass(unittest.TestCase):
def test_first(self):
my_class = MyClass()
result = my_class.test_method()
self.assertEqual(result, "successful")
if __name__ == '__main__':
unittest.main()
Results
When running test_my_class.py the result are the following.
AssertionError: <MagicMock name='mock.ExternalClass.test_method()' id='140272215184664'> != 'successful'
Clearly since the external_module is mocked, even MyClass becomes an instance of a mock-object.
Similar posts
The problem is similar to as described in Python mock: mocking base class for inheritance, but has the difference that the base_class is from an external module.
Even How to mock a base class with python mock library show som similarities to my problem, though the solutions can not be directly applied.
Tries and failures
To get the import
import external_module
to work in my_class.py
sys.modules['external_module'] = MagicMock()
need to be set in test_my_class.py.
Though, this leads to that external_module.* becomes a Mock-instance.
You could create a helper module mocked_external_module, which can be imported from your tests and also contains a class base_class. Then you do the following in your test code:
import mocked_external_module
sys.modules['external_module'] = mocked_external_module
Plus, every method of your base_class that you need to mock you can create as a Mock or MagicMock.

Python unittest - testing multiple files with the same name

I am attempting to test multiple functions with the same name and signature. (Student assignment submissions.) My setup currently looks like this:
TestCases/
__init__
testcase1.py
submission1/
func.py
submission2/
func.py
submission3/
func.py
Each func.py contains a function named foo. I would like to test each occurrence of foo with the same test suite. The standard unittest setup of
import unittest
from func import foo
class TestFoo(unittest.TestCase):
def test_empty(self):
self.assertTrue(foo(''))
...
if __name__ == '__main__':
unittest.main()
has (unsurprisingly) problems with the from func import foo line since func is not in the same folder, and I cannot seem to get it to pick up the proper import dynamically.
It struck me that I could take advantage of having already extended the base class and eliminate the need for the import by doing something like:
import unittest
class TestFoo(unittest.TestCase):
def __init__(self, foo):
self.foo = foo
def test_empty(self):
self.assertTrue(self.foo(''))
and in my test program instantiate a TestFoo object and pass it the function to test:
tf = TestFoo(foo)
but then I haven't been able to properly define and call the test suite.
Or is there an entirely different approach that would do what I need?

Patch over a function imported inside another function

In order to avoid a circular import, I've been forced to define a function that looks like:
# do_something.py
def do_it():
from .helpers import do_it_helper
# do stuff
Now I'd like to be able to test this function, with do_it_helper patched over. If the import were a top level import,
class Test_do_it(unittest.TestCase):
def test_do_it(self):
with patch('do_something.do_it_helper') as helper_mock:
helper_mock.return_value = 12
# test things
would work fine. However, the code above gives me:
AttributeError: <module 'do_something'> does not have the attribute 'do_it_helper'
On a whim, I also tried changing the patch statement to:
with patch('do_something.do_it.do_it_helper') as helper_mock:
But that produced a similar error. Is there any way to mock this function, given the fact that I'm forced into importing it within the function where it's used?
You should mock out helpers.do_it_helper:
class Test_do_it(unittest.TestCase):
def test_do_it(self):
with patch('helpers.do_it_helper') as helper_mock:
helper_mock.return_value = 12
# test things
Here's an example using mock on os.getcwd():
import unittest
from mock import patch
def get_cwd():
from os import getcwd
return getcwd()
class MyTestCase(unittest.TestCase):
#patch('os.getcwd')
def test_mocked(self, mock_function):
mock_function.return_value = 'test'
self.assertEqual(get_cwd(), 'test')

Categories

Resources