I would like to mock a method which is in the init.py, but actually it is not working.
There is an example to demonstrate the issue and how I tried to write the unit test:
The code under test: src.main.myfile:
from src.main.utils import a_plus_b
def method_under_test():
a_plus_b()
The a_plus_b is in the __init__.py in the src.main.utils module:
def a_plus_b():
print("a + b")
The unittest:
import src.main.utils
import unittest
from mock import patch
from src.main.myfile import method_under_test
class my_Test(unittest.TestCase):
def a_plus_b_side_effect():
print("a_plus_b_side_effect")
#patch.object(utils, 'a_plus_b')
def test(self, mock_a_plus_b):
mock_a_plus_b.side_effect = self.a_plus_b_side_effect
method_under_test()
The unit test prints the "a + b", and not the side effect. Could anyone help me out what I did wrong?
The name you need to patch isn't src.main.utils.a_plus_b, but src.main.myfile.a_plus_b, since that is what method_under_test uses.
#patch('src.main.myfile.a_plus_b')
def test(self, mock_a_plus_b):
mock_a_plus_b.side_effect = self.a_plus_b_side_effect
method_under_test()
Related
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.
I have some problem with mocking and testing my function/class.
I think the easiest way to explain it will be showing some code:
module.py:
import some_module
class MyClassToTest:
def __init__():
pass
def f(self, a):
if isinstance(a, some_module.someClass):
print("true")
I want to test my module.py:
test_module.py:
import sys
from unittest import mock
sys.modules['some_module'] = mock.MagicMock() # mocking whole module
def test_my_test():
to_test = MyClassToTest()
to_test.f(5)
Of course here I will have an error:
TypeError: isinstance() arg 2 must be a type or tuple of types
because some_module was mocked.
I really need to mock the "some_module" - it's needed for some other tests.
And yes, I probably should unmock the module just for some tests or maybe mock the isinstance when I'm using it in specific function?
Do you have any idea how to process it?
You are in the right way, you've just forgot to set the object spec. Here is a fully functional example based in your question.
some_module.py
class SomeModule(object):
def method(self):
return True
module.py
import some_module
class MyClass:
def __init__(self):
pass
def f(self, a):
if isinstance(a, some_module.SomeModule):
return True
return False
test_module.py
from unittest import mock
from unittest import main, TestCase
import function
class MyClassTestCase(TestCase):
def test_myclass(self):
m = mock.MagicMock(spec=function.some_module.SomeModule)
mc = function.MyClass()
self.assertEqual(mc.f(1), False)
So I've this code that mocks two times, the first time by mocking imports with:
sys.modules['random'] = MagicMock()
The second time happens inside the unittest of a function that used that import, for example a function that used random
The tests. py is:
import sys
import unittest
from unittest import mock
from unittest.mock import MagicMock
import foo
sys.modules['random'] = MagicMock()
class test_foo(unittest.TestCase):
def test_method(self):
with mock.patch('random.choice', return_value = 2):
object = foo.FooClass(3)
self.assertEqual(2, object.method(), 'Should be 2')
def test_staticmethod(self):
with mock.patch('random.choice', return_value = 2):
object = foo.FooClass(3)
self.assertEqual(2, object.method(), 'should be 2')
The original file Foo.py is:
import random
class FooClass:
def __init__(self,arg):
self.arg = arg
def method(self):
print(random.choice)
return random.choice([1,2,3])
#staticmethod
def staticmethod():
print(random.choice)
random.choice([1,2,3])
The two mocks contrarrest each other, and the mocking of random doesn't happen.
When it prints random it actually prints:
<<bound method Random.choice of <random.Random object at 0x7fe688028018>>
I want that to print a MagicMock.
Can someone help me understand what's happening? Why are they contrarresting each other?
You don't need to update the module source with sys.modules['random'] = MagicMock() without this line it works fine <MagicMock name='choice' id='...'>. patch already does all the work for the isolated temporary updating the method. See more explanation in the docs - Where to patch
so i've got a problem with my code.
File 1:
class Abc(object):
...
def function1(self):
#do something
def function2(self):
x = input()
return x+1
and now i'm trying to test function 2 so i wrote a test for it and i don't know what i am doing wrong:
from unittest.mock import patch
import unittest
from file1 import *
class TestBackend(unittest.TestCase):
def test_mode_first(self):
self.assertEqual(Abc().funcion1(), 30)
#patch('funcion2.input', create=True)
def test_mode_second(self, mocked_input):
mocked_input.side_effect = ["QWE"]
result = Abc().funcion2()
self.assertEqual(result, 10)
if __name__ == '__main__':
unittest.main()
i get ModuleNotFoundError: No module named 'function2'
so what i am doing wrong in here?
thanks for your help :)
You get ModuleNotFoundError because funcion2 is not a module. patch doc is clear about this:
target should be a string in the form 'package.module.ClassName'. The
target is imported and the specified object replaced with the new
object, so the target must be importable from the environment you are
calling patch() from. The target is imported when the decorated
function is executed, not at decoration time.
This works for me when executed with python3 -m unittest discover from the directory the files are in.
BTW you have a couple of typos in your example, e.g. Abc().funcion2(), note the missing t in funcion2.
Also, try not to use from … import *: https://docs.quantifiedcode.com/python-anti-patterns/maintainability/from_module_import_all_used.html#using-wildcard-imports-from-import
# file1.py
class Abc(object):
def function1(self):
return 30
def function2(self):
x = input()
return x + "1"
# test_file1.py
import unittest
from unittest.mock import patch
from file1 import Abc
class TestBackend(unittest.TestCase):
def test_mode_first(self):
self.assertEqual(Abc().function1(), 30)
#patch('builtins.input')
def test_mode_second(self, mocked_input):
mocked_input.return_value = "QWE"
result = Abc().function2()
self.assertEqual(result, "QWE1")
I am currently attempting to write unit tests for my Main.py's main() function
Here is a simplified version of my Main.py:
from Configuration import Configuration # Configuration.py is a file in the same dir
def main():
try:
Configuration('settings.ini')
except:
sys.exit(1) # Test path1
sys.exit(0) # Test path2
if __name__ == '__main__':
main()
In my Unit Tests\MainUnitTests.py I want to import ..\Main.py and fake the Configuration class in such a way that I can hit Test path1 and Test path2
I found that i can assert sys.exit() with the following:
with self.assertRaises(SystemExit) as cm:
main()
self.assertEqual(cm.exception.code, 1)
but I am having trouble overriding the from Configuration import Configuration
Thoughts?
So far I have tried the following within Unit Tests\MainUnitTests.py:
class FakeFactory(object):
def __init__(self, *a):
pass
sys.modules['Configuration'] = __import__('FakeFactory')
class Configuration(FakeFactory):
pass
Another example for demonstration:
foo.py:
from bar import a,b
x = a()
class a(object):
def __init__(self):
self.q = 2
y = a()
print x.q, y.q # prints '1 2' as intended
b() # I want this to print 2 without modifying bar.py
bar.py:
class a(object):
def __init__(self):
self.q = 1
def b():
t = a()
print t.q
when you use the import
from bar import a
it import the name directly into the module, so monkeypatching bar won't help, you need to override a directly in the main file:
def fake_a():
print("OVERRIDEN!")
main.a = fake_a
Do know that unittest has helper functions for this in the mock subpackage, I believe you could do something like:
from unittest.mock import patch
...
with patch("main.a", fake_a) as mock_obj: #there are additional things you can do with the mock_obj
do_stuff()
This would work in your first example with configuration since the class that needs to be patched is not used in the global scope although foo uses bar.a as soon as it is loaded so you would need to patch it before even loading foo:
from unittest.mock import patch
...
with patch("bar.a", fake_a) as mock_obj: #there are additional things you can do with the mock_obj
import foo #now when it loads it will be loaded with the patched name
However in this case foo.a would not be reverted at the end of the with block because it can't be caught by unittest... I really hope your actual use case doesn't use the stuff to be patched at module level.