Python mock failing call assertion - python

I have been reading into python mocking but can't get my head around why the following code is failing.
I have two classes, a Potato and a PotatoBag like the following. Figure is stored in food.py and Report is stored in bag.py.
class Potato:
def create_potato(self):
pass
def output_potato(self):
pass
class PotatoBag:
def __init__(self, potatoes):
self.potatoes = potatoes
def output_to_file(self):
for fig in self.potatoes:
fig.create_potato()
fig.output_potato()
Currently I am trying to unit test the output method so that Report correctly calls create_figure and output_figure from Figure using a mock. This is my test code:
from unittest.mock import MagicMock, patch
from bag import PotatoBag
from food import Potato
import pytest
#pytest.fixture(scope='module')
def potatoes():
x = Potato()
y = Potato()
return [x, y]
#patch('food.Potato')
def test_output_to_file(mock_potato, potatoes):
test_potato_bag = PotatoBag(potatoes)
test_potato_bag.output_to_file()
mock_potato.return_value.create_potato.assert_called()
mock_potato.return_value.output_potato.assert_called()
Immediately pytest yields an AssertionError stating that create_figure was never called.
_mock_self = <MagicMock name='Potato().create_potato' id='140480853451272'>
def assert_called(_mock_self):
"""assert that the mock was called at least once
"""
self = _mock_self
if self.call_count == 0:
msg = ("Expected '%s' to have been called." %
self._mock_name or 'mock')
> raise AssertionError(msg)
E AssertionError: Expected 'create_potato' to have been called.
/home/anaconda3/lib/python3.7/unittest/mock.py:792: AssertionError
What is wrong with my code?

You are passing the Report a list of Figures from your fixture instead of a mock.
Changing your test to...
#patch('figure.Figure')
def test_output_to_file(mock_figure, figures):
test_report = Report([mock_figure])
test_report.output_to_file()
mock_figure.create_figure.assert_called_once()
mock_figure.output_figure.assert_called_once()
This resolves testing that output_to_file correctly is calling the functions on Figure without actually worrying about setting up a figure and dealing with any side effects or additional complexities that may come with calling those functions. The worries of that can be saved for the unit tests for Figure ;)

Related

Hard to understand return_value for Python Mock Class in the following example

I had a hard time understanding the choice of using return_value. See the following example
# my_module.py
def query(connection):
cursor = connection.cursor()
cursor.execute("SELECT name FROM users WHERE id = 1")
return cursor.fetchone()[0]
# test_query.py
import pytest
from unittest.mock import Mock
# Import the module containing the function to test
import my_module
def test_query():
# Set up the mock database connection
connection = Mock()
cursor = connection.cursor.return_value
cursor.fetchone.return_value = ("Alice",)
# Call the function under test
result = my_module.query(connection)
# Assert that the function has the expected behavior
assert result == "Alice"
My question is in test_query.py, why do I need the first return_value? It looks like if cursor = connection.cursor.return_value, I can replace the cursor in cursor.fetchone.return_value = ("Alice",), which then becomes connection.cursor.return_value.fetchone.return_value = ("Alice",)? Why does this code require two return_value ?
Without the first return_value, I received TypeError: 'Mock' object is not subscriptable after running pytest
The broader question is when should I use return_value?
First lets make sure we understand what Mock is. You can think of it as an object that's every attribute or function call returns another object of type Mock.
from unittest.mock import Mock
m = Mock()
for cls in [m, m.any_attribute, m.any_function("any_param"), m.any_function.return_value]:
assert isinstance(cls, Mock)
We can easilly implement such behaviour by ourself:
class MyMock:
def __getattribute__(self, _: str):
return MyMock()
def __call__(self, *args, **kwargs):
return MyMock()
However return_value has a special meaning. With return value you ask for (or set by yourself) result of calling specified function.
Now, what our goal is? Our goal is to make cursor.fetchone() function return something that is subscriptable (because we ask for first element with [0]). But with such syntax:
connection.cursor.fetchone.return_value = ("Alice, )
We are mocking result of calling fetchone from connection.cursor, not connection.cursor(). It would work fine only if cursor is a property.
To sum up, whenever you see a function call you use return_value and in case of attributes you don't.

Assertion Error when logging.exception(error)

I have this function in a script called mymodule.py
import logging
def foo():
try:
raise ConnectionError('My Connection Error')
except ConnectionError as ce:
logging.exception(ce)
And I have the test for it called test_mymodule.py:
import unittest
import unittest.mock as um
import mymodule
class TestLoggingException(unittest.TestCase):
#um.patch('mymodule.logging')
def test_connection_error_correctly_logged_without_raising(self, mock_logging):
mymodule.foo()
mock_logging.assert_has_calls(
[um.call(ConnectionError('My Connection Error'))]
)
However, when running test_mymodule.py, the below assertion error is raised.
AssertionError: Calls not found.
Expected: [call(ConnectionError('My Connection Error'))]
Actual: [call(ConnectionError('My Connection Error'))]
Why is it thinking they are different and how could I work around this?
The problem is that two instances of ConnectionError, even if create with the same arguments, are not equal.
You create two instances in your code : in foo and in the um.call().
However, those two instance are not the same, and are therefore not equal. You can illustrate that simply:
>>> ConnectionError("test") == ConnectionError("test")
False
One solution is to check which calls were made to the mockup. The calls are exposed through a variable called mockup_calls.
Something like this
class TestLoggingException(unittest.TestCase):
#um.patch('mymodule.logging')
def test_connection_error_correctly_logged_without_raising(self, mock_logging):
mymodule.foo()
print("Calls are: ", mock_logging.mock_calls)
# Check that logging was called with logging.exception(ConnectionError("My Connection Error"))
calls = mock_logging.mock_calls
assert(len(calls) == 1)
# Unpack call
function_called, args, kwargs = calls[0]
assert(function_called == "exception")
connection_error = args[0]
assert(isinstance(connection_error, ConnectionError))
assert(connection_error.args[0] == "My Connection Error") # This will depend on how ConnectionError is defined
What is tricky about this example is that it would work with types that evaluate equal even if they are not the same, like str("hi" == "hi" will yield True), but not most classes.
Does it help ?

PyTest-Mock not working due to AttributeError

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'

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

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

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.

Categories

Resources