Mock a function passed as default parameter - python-3.x

I have a python file (a.py) which defines a function, and a class which uses it as a default parameter in it's init method and initializes another imported class. This is my a.py
import OtherClass
def useful_default_func():
//do something useful
class MyClass(object):
def __init__(self, def_func=useful_default_func):
self.other_class = OtherClass(def_func())
//do something useful
I am trying to mock the useful_default_func in my test file.
class TestMyClass(unittest.TestCase):
#patch('a.useful_default_func')
#patch('a.OtherClass')
test_init(self, mock_other_class, mock_default_func):
myc= MyClass()
mock_other_class.assert_called_once_with(mock_default_func)
// further tests
However, the mock_default_func is not patching and my test fails saying,
Expected: OtherClass(<MagicMock id='xxx'>)
Actual: OtherClass(<function useful_default_func at 0x7f155858b378>)
Fairly new to the python mock lib, so not really sure, what is happening here or what I am doing wrong or how should I be approaching it?

Something like this could work:
def mocked_fct():
return 42
class TestMyClass(unittest.TestCase):
#mock.patch.object(a.MyClass.__init__, '__defaults__', (mocked_fct,))
#patch('a.OtherClass')
def test_init(self, mock_other_class):
myc = MyClass()
mock_other_class.assert_called_once_with(mocked_fct)
I didn't use a mock for the mocked default function here, this can be changed if needed. The main point is that you can mock the function defaults.
Note: this assumes that you call
self.other_class = OtherClass(def_func)
instead of
self.other_class = OtherClass(def_func())
If this was not a typo, your assertion would not be correct. In this case, you could instead use:
mock_other_class.assert_called_once_with(mocked_fct())
mock_other_class.assert_called_once_with(42) # same as above

Related

Mocking method from another class with python unittest mock

I have a class and within that class there are used methods from another classes. Let's say it looks like that (just an example):
from jobs.fruit import Fruit
from jobs.veggie import Veggie
class Healthy:
def start(self, arg):
self.start_connection()
if len(arg) == 1 or self.check(arg):
fruit = Fruit()
fruit.start()
return 1
else:
veggie = Veggie()
veggie.update()
return 0
I'm writting unit tests for my Healthy class, and the start looks like this:
def test_start():
healthy = Healthy()
healthy.start_connection = MagicMock(return_value=1)
healthy.check = MagicMock(return_value=True)
fruit = Fruit()
fruit.start = MagicMock(return_value = 0)
result = healthy.start()
Unfortunately when running the test it doesn't take fruit.start mocked value, but it tries to run original method. How should I mock that class or method and pass that info to the method that I'm testing? I never had to mock something from outside the class that I'm testing and got a bit stuck with it. I'd prefer to stick to unittest mock to not mess with several methods in the code so it's clear for my coworkers. Thanks in advance!

Correct way to mock decorated method in Python

I have the class with .upload method. This method is wrapped using the decorator:
#retry(tries=3)
def upload(self, xml_file: BytesIO):
self.client.upload(self._compress(xml_file))
I need to test if it runs 3 times if some exception occurs.
My test looks like:
#mock.patch("api.exporter.jumbo_uploader.JumboZipUploader.upload")
def test_upload_xml_fail(self, mock_upload):
"""Check if decorator called the compress function 3 times"""
generator = BrandBankXMLGenerator()
file = generator.generate()
uploader = JumboZipUploader()
uploader.upload = retry(mock_upload)
mock_upload.side_effect = Exception("Any exception")
uploader.upload(file)
self.assertEqual(mock_upload.call_count, 3)
I have read that the default behavior of python decorators assumes that the function inside the test will be unwrapped and I need to wrap it manually.
I did that trick, but the code fails with AssertionError: 0 != 3.
So, what is the right way here to wrap the decorated method properly?

How can I access the current executing module?

I want to access the calling environment from an imported module.
import child
…
def test(xxx):
print("This is test " + str(xxx))
child.main()
…
now on child:
import inspect
def main():
caller = inspect.currentframe().f_back
caller.f_globals['test']("This is my test")
This works, but it's not fancy. Is there a simplification like 'self' when use in a class? the idea is to do: caller.test('abc') instead.
One option to pass the caller as a parameter like: child.main(self), however self is not available in this context.
Python only load one version of a module so, tempted with this idea:
import sys
myself=sys.modules[__name__]
a then sending myself to the child:
…
child.main(myself)
…
Creates a reference to (a new) module, but not the running one, this is like creating a new class: one code buy a different environment.
If you already have a way of accessing the correct functions and data that works, why not just store f_globals on an instance of a wrapper class and then call things from the instance as if they were unbound properties? You could use the class itself, but using an instance ensures that the data you get from the imported file are valid when you create the object. Then you can access using the dot operator the way you want. This is your child file:
import inspect
class ImportFile:
def __init__(self, members):
self.__dict__.update(members)
def main():
caller = inspect.currentframe().f_back
imported_file = ImportFile(caller.f_globals)
imported_file.test("This is my test")
Outputs:
This is test This is my test
Admittedly, I don't have your setup, importantly the module you're trying to pull from, so it's hard to confirm whether or not this will work for you even though it has for me, but I think you could also use your method of calling main with globals() or even inspect.getmembers() since while inside the module you're importing you're still on the frame you're accessing with f_back from inside child.
The imported module:
import child
def test(xxx):
print("This is test " + str(xxx))
child.main(globals())
Child:
import inspect
class ImportFile:
def __init__(self, members):
self.__dict__.update(members)
def main(caller):
imported_file = ImportFile(caller)
imported_file.test("This is my test")
Outputs:
This is test This is my test

python3 mock member variable get multiple times

I have a use case where I need to mock a member variable but I want it to return a different value every time it is accessed.
Example;
def run_test():
myClass = MyDumbClass()
for i in range(2):
print(myClass.response)
class MyDumbClass():
def __init__(self):
self.response = None
#pytest.mark.parametrize("responses", [[200,201]])
#patch("blah.MyDumbClass")
def test_stuff(mockMyDumbClass, responses)
run_test()
assert stuff
What I am hoping for here is in the run_test method the first iteration will print 200 then the next will print 201. Is this possible, been looking through unittest and pytest documentation but can't find anything about mocking a member variable in this fashion.
Just started learning pytest and unittest with python3 so forgive me if the style isn't the best.
If you wrap myDumbClass.response in a get function - say get_response() then you can use the side_effect parameter of the mock class.
side_effect sets the return_value of the mocked method to an iterator returning a different value each time you call the mocked method.
For example you can do
def run_test():
myClass = MyDumbClass()
for i in range(2):
print(myClass.get_response())
class MyDumbClass():
def __init__(self):
self.response = None
def get_response(self):
return self.response
#pytest.mark.parametrize("responses", [([200,201])])
def test_stuff( responses):
with mock.patch('blah.MyDumbClass.get_response', side_effect=responses):
run_test()
assert False
Result
----------------------------------- Captured stdout call ------------------------------------------------------------
200
201
Edit
No need to patch via context manager e.g with mock.patch. You can patch via decorator in pretty much the same way. For example this works fine
#patch('blah.MyDumbClass.get_response',side_effect=[200,100])
def test_stuff(mockMyDumbClass):
run_test()
assert False
----------------------------------- Captured stdout call ------------------------------------------------------------
200
201

How to mock objects of a Python class?

Lets say I the following class;
class CompositionClass(object):
def __init__(self):
self._redis = Redis()
self._binance_client = BinanceClient()
def do_processing(self, data):
self._redis.write(data)
self._binance_client.buy(data.amount_to_buy)
# logic to actually unittest
return process_val
I have other objects which call external API as composition in my ComplexClass. When I am unittesting the logic of do_processing, I do not want to call these expensive API calls. I have checked throughly in SO and Google about unittesting; all examples are simple not that really useful. In my case how can I use unittest.mock to mock these objects?
One way of mocking the Redis and BinanceClient classes is to use the patch decorator in your test class, such as:
from unittest import TestCase
from unittest.mock import patch
from package.module import CompositionClass
class TestCompositionClass(TestCase):
#patch('package.module.BinanceClient')
#patch('package.module.Redis')
def test_do_processing(self, mock_redis, mock_binance):
c = CompositionClass()
data = [...]
c.do_processing(data)
# Perform your assertions
# Check that mocks were called
mock_redis.return_value.write.assert_called_once_with(data)
mock_binance.return_value.buy.assert_called_once_with(data.amount_to_buy)
Note that the path specified to #patch is the path to module containing the CompositionClass and its imports for Redis and BinanceClient. The patching happens in that module, not the module containing the Redis and BinanceClient implementations themselves.
You need to set a value that must be returned by your API call, to this function:
from unittest.mock import MagicMock
class Tester(unittest.TestCase):
def setUp(self):
pass
def test_do_processing(self):
self.API_function = MagicMock(return_value='API_response')
# test logic

Resources