src/mainDir/mainFile.py
contents of mainFile.py
import src.tempDir.tempFile as temp
data = 'someData'
def foo(self):
ans = temp.boo(data)
return ans
src/tempDir/tempFile.py
def boo(data):
ans = data
return ans
Now I want to test foo() from src/tests/test_mainFile.py and I want to mock temp.boo(data) method in foo() method
import src.mainDir.mainFile as mainFunc
testData = 'testData'
def test_foo(monkeypatch):
monkeypatch.setattr('src.tempDir.tempFile', 'boo', testData)
ans = mainFunc.foo()
assert ans == testData
but I get error
AttributeError: 'src.tempDir.tempFile' has no attribute 'boo'
I expect ans = testData.
I would like to know if I am correctly mocking my tempDir.boo() method or I should use pytest's mocker instead of monkeypatch.
You're telling monkeypatch to patch the attribute boo of the string object you pass in.
You'll either need to pass in a module like monkeypatch.setattr(tempFile, 'boo', testData), or pass the attribute as a string too (using the two-argument form), like monkeypatch.setattr('src.tempDir.tempFile.boo', testData).
My use case was was slightly different but should still apply. I wanted to patch the value of sys.frozen which is set when running an application bundled by something like Pyinstaller. Otherwise, the attribute does not exist. Looking through the pytest docs, the raising kwarg controls wether or not AttributeError is raised when the attribute does not already exist. (docs)
Usage Example
import sys
def test_frozen_func(monkeypatch):
monkeypatch.setattr(sys, 'frozen', True, raising=False)
# can use ('fq_import_path.sys.frozen', ...)
# if what you are trying to patch is imported in another file
assert sys.frozen
Update: mocking function calls can be done with monkeypatch.setattr('package.main.slow_fun', lambda: False) (see answer and comments in https://stackoverflow.com/a/44666743/3219667) and updated snippet below
I don't think this can be done with pytest's monkeypatch, but you can use the pytest-mock package. Docs: https://github.com/pytest-dev/pytest-mock
Quick example with the two files below:
# package/main.py
def slow_fun():
return True
def main_fun():
if slow_fun():
raise RuntimeError('Slow func returned True')
# tests/test_main.py
from package.main import main_fun
# Make sure to install pytest-mock so that the mocker argument is available
def test_main_fun(mocker):
mocker.patch('package.main.slow_fun', lambda: False)
main_fun()
# UPDATE: Alternative with monkeypatch
def test_main_fun_monkeypatch(monkeypatch):
monkeypatch.setattr('package.main.slow_fun', lambda: False)
main_fun()
Note: this also works if the functions are in different files
Related
I'm trying to test this kind of method that alters my incoming object, but it needs to be mocked because I can't run the code in a unit test case (requires massive files that aren't in the repo). I want to mock method_to_mock so that it sets the file_wrapper_object.status to whatever I want for that test case, notice the method doesn't return anything.
def method_to_mock(file_wrapper_object):
try:
process(file_wrapper_object) #<—- I can't run this in a test case
file_wrapper_object.status = "COMPLETE"
except:
file_wrapper_object.status = "FAILED"
def TestProcessFileWrapperObject(self):
file_wrapper_object = create_wrapper_object(arbitrary_data)
method_to_mock(file_wrapper_object)
self.assertEqual(file_wrapper_object.status, "COMPLETE")
How can I mock a method that doesn't return anything but alters the incoming object?
You can use the side_effect to specify a custom behavior for the mock object. Here is an example:
import unittest
from unittest.mock import MagicMock
def TestProcessFileWrapperObject(self):
file_wrapper_object = create_wrapper_object(arbitrary_data)
my_method_mock = MagicMock(return_value=None)
def side_effect(file_wrapper_object):
file_wrapper_object.status = "COMPLETE"
my_method_mock.side_effect = side_effect
my_method = my_method_mock
my_method(file_wrapper_object)
self.assertEqual(file_wrapper_object.status, "COMPLETE")
Please check this for more details.
I'am having problems testing a function that has a decorator for caching:
#retry(stop=stop_after_attempt(3))
#cache.memoize(60)
def get_azure_machine_info(rg_name, machine_name, expand="instanceView"):
try:
compute_client = get_azure_compute_client()
return compute_client.virtual_machines.get(rg_name, machine_name, expand=expand)
except CloudError:
return None
My test:
#patch("dev_maintenance.machines.get_azure_compute_client")
def test_get_azure_machine_info(get_azure_compute_client):
cache.delete_memoized('get_azure_machine_info')
with app.app_context():
ret = get_azure_machine_info("rg1", "m1")
get_azure_compute_client.assert_called_once()
assert len(get_azure_compute_client.return_value.method_calls) == 1
assert (
ret == get_azure_compute_client.return_value.virtual_machines.get.return_value
)
get_azure_compute_client.return_value.virtual_machines.get.assert_called_once_with(
"rg1", "m1", expand="instanceView"
)
Before i used cache the test was working fine, but now i can't figure out what is going on here.
The error:
The cache is trying to do its thing with the MagicMock objects that patch is creating. And it's failing since it 'can't pickle class unitest.mock.MagicMock'
The simplest work-around would be to mock out the cache module in the test. You can look here for pointers: Can I patch a Python decorator before it wraps a function?
Do this in a setUp() fixture.
I am trying to use PyTest_Mock in order to do some testing in my Python project. I created a very simple test to try it out, but I am getting an AttributeError and I don't know why.
model.py
def square(x):
return x * x
if __name__ == '__main__':
res = square(5)
print("result: {}".format(res))
test_model.py
import pytest
from pytest_mock import mocker
import model
def test_model():
mocker.patch(square(5))
assert model.square(5) == 25
After running python -m pytest I get a failure and the following error:
def test_model():
> mocker.patch(square(5))
E AttributeError: 'function' object has no attribute 'patch'
test_model.py:7: AttributeError
You don't need to import mocker, it's available as fixture, so you just pass it as a parameter in the test function:
def test_model(mocker):
mocker.patch(...)
square(5) evaluates to 25, so mocker.patch(square(5)) will effectively try to patch a number 25. Instead, pass the function name as parameter: either
mocker.patch('model.square')
or
mocker.patch.object(model, 'square')
Once patched, square(5) will not return 25 anymore since the original function is replaced with a mock object that can return anything and will return a new mock object by default. assert model.square(5) == 25 will thus fail. Usually, you patch stuff either to avoid complex test setup or simulate behaviour of components that is desired in test scenario (for example, a website being unavailable). In your example, you don't need mocking at all.
Complete working example:
import model
def test_model(mocker):
mocker.patch.object(model, 'square', return_value='foo')
assert model.square(5) == 'foo'
I have a module with a dictionary as associative array to implement a kind-of switch statement.
def my_method1():
return "method 1"
def my_method2():
return "method 2"
map_func = {
'0': my_method1,
'1': my_method2
}
def disptach(arg):
return map_func[arg]()
How can I mock my_method1 in tests? I've tried the following without success:
import my_module as app
#patch('my_module.my_method1')
def test_mocking_sample(self, my_mock):
my_mock.return_value = 'mocked'
assert_equal('mocked',app.dispatch('0'))
Any idea?
This piece of patch documentation says the following:
patch works by (temporarily) changing the object that a name points to
with another one. There can be many names pointing to any individual
object, so for patching to work you must ensure that you patch the
name used by the system under test.
Basically, your dispatcher won't see it, as the mapping is built to reference the original method, before the patch is applied.
The simplest thing you can do to make it mockable is to fold the mapping into the dispatch function:
def dispatch(arg):
return {
'0': my_method1,
'1': my_method2
}[arg]()
This does have the downside that it rebuilds that mapping every time you call it, so it will be slower.
Trying to get a bit clever, it seems that Python lets you swap out the actual code of a function, like so:
>>> f = lambda: "foo"
>>> a = f
>>> g = lambda: "bar"
>>> f.func_code = g.func_code
>>> a()
'bar'
I won't recommend that you do it this way, but maybe you can find a mocking framework that supports something similar.
As you've discovered, patching my_Method1() does not work. This is because map_func['0'] was defined when my_module was imported and subsequent changes to my_Method1() do not update map_func for your test. Instead, we need to patch the value in dictionary map_func for key '0' directly. The unittest.mock documentation explains how to patch a dictionary entry. Below is a working implementation of your test:
""" test_my_module.py
"""
import unittest
import unittest.mock as mock
import my_module as app
my_mock = mock.MagicMock()
class Test_mock_sample(unittest.TestCase):
#mock.patch.dict('my_module.map_func', {'0': my_mock})
def test_mocking_sample(self):
my_mock.return_value = 'mocked'
self.assertEqual('mocked', app.dispatch('0'))
if __name__ == '__main__':
unittest.main()
After changing disptach to dispatch in your original my_module...
""" my_module.py
"""
def my_method1():
return "method 1"
def my_method2():
return "method 2"
map_func = {
'0': my_method1,
'1': my_method2
}
def dispatch(arg):
return map_func[arg]()
Then the command python -m unittest test_my_module gives the following output:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
It worked!
I am trying to mock the urllib2.urlopen library in a way that I should get different responses for different urls I pass into the function.
The way I am doing it in my test file now is like this
#patch(othermodule.urllib2.urlopen)
def mytest(self, mock_of_urllib2_urllopen):
a = Mock()
a.read.side_effect = ["response1", "response2"]
mock_of_urllib2_urlopen.return_value = a
othermodule.function_to_be_tested() #this is the function which uses urllib2.urlopen.read
I expect the the othermodule.function_to_be_tested to get the value "response1" on first call and "response2" on second call which is what side_effect will do
but the othermodule.function_to_be_tested() receives
<MagicMock name='urlopen().read()' id='216621051472'>
and not the actual response. Please suggest where I am going wrong or an easier way to do this.
The argument to patch needs to be a description of the location of the object, not the object itself. So your problem looks like it may just be that you need to stringify your argument to patch.
Just for completeness, though, here's a fully working example. First, our module under test:
# mod_a.py
import urllib2
def myfunc():
opened_url = urllib2.urlopen()
return opened_url.read()
Now, set up our test:
# test.py
from mock import patch, Mock
import mod_a
#patch('mod_a.urllib2.urlopen')
def mytest(mock_urlopen):
a = Mock()
a.read.side_effect = ['resp1', 'resp2']
mock_urlopen.return_value = a
res = mod_a.myfunc()
print res
assert res == 'resp1'
res = mod_a.myfunc()
print res
assert res == 'resp2'
mytest()
Running the test from the shell:
$ python test.py
resp1
resp2
Edit: Whoops, initially included the original mistake. (Was testing to verify how it was broken.) Code should be fixed now.