Mocking urllib2.urlopen().read() for different responses - python

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.

Related

Python mock testing: How do I alter an Object in a mocked method?

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.

Testing a method call inside a function

I need to mock the object whose method I call in a function. The object is initialized inside the function and that's the problem. How to replace its implementation with mock?
Function code:
def handler (event, context):
"""Function, when call Yandex Server less"""
function_heandler = Handler(event=event, context=context)
response = function_heandler.run()
return response
Test code:
def test_main_call_handler():
with mock.patch('function.handler.Handler', new=mock.MagicMock()) as mock_handler:
handler({}, object())
mock_handler.run.assert_called()
And this, as expected, does not work. The function will be called in another module and I cannot pass the mock object there. Any ideas on how to fix this?
You should mock the class Handler instead.
Assuming Handler is imported from package.module, you can simply patch package.module.Handler:
def test_main_call_handler():
with mock.patch('package.module.Handler') as mock_handler:
handler({}, object())
mock_handler.run.assert_called()
One thing to remember when mocking, is that you mock (replace) the attribute of the tested module and not the called object.
To be concrete, let's say that your tested module is named using_my_handler_module.py and it looks like this (the first import line is important):
from my_handler_module import Handler
def handler(event, context):
"""Function, when call Yandex Server less"""
function_heandler = Handler(event=event, context=context)
response = function_heandler.run()
return response
Now, you need to mock the Handler attribute of using_my_handler_module, so when it's used, it's mocked.
So the test function would look like so:
import mock
from using_my_handler_module import handler
def test_main_call_handler():
with mock.patch('using_my_handler_module.Handler', new=mock.MagicMock()) as mock_handler:
handler({}, object())
mock_handler.return_value.run.assert_called()
Note we patch 'using_my_handler_module.Handler' .
Next, we check the assert called like this: mock_handler.return_value.run.assert_called()
The reason is that mock_handler.return_value is matching the return object of Handler(event=event, context=context) and you execute the run method on that object, so need to append .run.assert_called().
I myself find that mocking is confusing at times. This is why I wrote a helper library on top of pytest mock, called pytest-mock-generator. Here is how you can use it for your case:
def test_main_call_handler_using_pytest_mock_generator(mocker, mg):
mg.generate_uut_mocks(handler)
handler({}, object())
You use the mg (mock generator) fixture to generate the mocks for you - simply send the tested function as a parameter to mg.generate_uut_mocks. When you execute the test function this output would be printed and copied to your clipboard:
# mocked dependencies
mock_Handler = mocker.MagicMock(name='Handler')
mocker.patch('using_my_handler_module.Handler', new=mock_Handler)
Copy it to the beginning of your test and you now have:
def test_main_call_handler_using_pytest_mock_generator(mocker, mg):
# mocked dependencies
mock_Handler = mocker.MagicMock(name='Handler')
mocker.patch('using_mock_fixture.using_my_handler_module.Handler', new=mock_Handler)
handler({}, object())
Next, you want to generate the asserts section, so use another mg function called generate_asserts - send it your new mock:
def test_main_call_handler_using_pytest_mock_generator(mocker, mg):
# mocked dependencies
mock_Handler = mocker.MagicMock(name='Handler')
mocker.patch('using_mock_fixture.using_my_handler_module.Handler', new=mock_Handler)
handler({}, object())
mg.generate_asserts(mock_Handler)
When you execute your test now, you would get a bunch of generated asserts:
assert 1 == mock_Handler.call_count
mock_Handler.assert_called_once_with(context=_object_object_at_0x7f7fa60c2790_, event={})
mock_Handler.return_value.run.assert_called_once_with()
You don't need most of them, copy the last line into your test and the final version of your working test would be like this one:
def test_main_call_handler_using_pytest_mock_generator(mocker):
# mocked dependencies
mock_Handler = mocker.MagicMock(name='Handler')
mocker.patch('using_mock_fixture.using_my_handler_module.Handler', new=mock_Handler)
handler({}, object())
mock_Handler.return_value.run.assert_called_once_with()

AttributeError: while using monkeypatch of pytest

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

How can I mock/patch an associative array in python

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!

Mocking ftplib.FTP for unit testing Python code

I don't know why I'm just not getting this, but I want to use mock in Python to test that my functions are calling functions in ftplib.FTP correctly. I've simplified everything down and still am not wrapping my head around how it works. Here is a simple example:
import unittest
import ftplib
from unittest.mock import patch
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
class TestDownloader(unittest.TestCase):
#patch('ftplib.FTP')
def test_download_file(self, mock_ftp):
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp.cwd.assert_called_with('pub/files')
When I run this, I get:
AssertionError: Expected call: cwd('pub/files')
Not called
I know it must be using the mock object since that is a fake server name, and when run without patching, it throws a "socket.gaierror" exception.
How do I get the actual object the fuction is running? The long term goal is not having the "download_file" function in the same file, but calling it from a separate module file.
When you do patch(ftplib.FTP) you are patching FTP constructor. dowload_file() use it to build ftp object so your ftp object on which you call login() and cmd() will be mock_ftp.return_value instead of mock_ftp.
Your test code should be follow:
class TestDownloader(unittest.TestCase):
#patch('ftplib.FTP', autospec=True)
def test_download_file(self, mock_ftp_constructor):
mock_ftp = mock_ftp_constructor.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp_constructor.assert_called_with('ftp.server.local')
self.assertTrue(mock_ftp.login.called)
mock_ftp.cwd.assert_called_with('pub/files')
I added all checks and autospec=True just because is a good practice
Like Ibrohim's answer, I prefer pytest with mocker.
I have went a bit further and have actually wrote a library which helps me to mock easily. Here is how to use it for your case.
You start by having your code and a basic pytest function, with the addition of my helper library to generate mocks to modules and the matching asserts generation:
import ftplib
from mock_autogen.pytest_mocker import PytestMocker
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
def test_download_file(mocker):
import sys
print(PytestMocker(mocked=sys.modules[__name__],
name=__name__).mock_modules().prepare_asserts_calls().generate())
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
When you run the test for the first time, it would fail due to unknown DNS, but the print statement which wraps my library would give us this valuable input:
...
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
...
import mock_autogen
...
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
I'm placing this in the test and would run it again:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
This time the test succeeds and I only need to collect the result of the second print to get the proper asserts:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch(__name__ + '.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftplib.FTP.assert_called_once_with('ftp.server.local')
mock_ftplib.FTP.return_value.login.assert_called_once_with()
mock_ftplib.FTP.return_value.cwd.assert_called_once_with('pub/files')
If you would like to keep using unittest while using my library, I'm accepting pull requests.
I suggest using pytest and pytest-mock.
from pytest_mock import mocker
def test_download_file(mocker):
ftp_constructor_mock = mocker.patch('ftplib.FTP')
ftp_mock = ftp_constructor_mock.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
ftp_constructor_mock.assert_called_with('ftp.server.local')
assert ftp_mock.login.called
ftp_mock.cwd.assert_called_with('pub/files')

Categories

Resources