Mocking a Standard Library function with and without pytest-mock - python

For testing purposes I would like to mock shutil.which (Python 3.5.1), which is called inside a simplified method find_foo()
def _find_foo(self) -> Path:
foo_exe = which('foo', path=None)
if foo_exe:
return Path(foo_exe)
else:
return None
I'm using pytest for implementing my test cases. Because of that I also would like to use the pytest extension pytest-mock. In the following I pasted an example testcase using pytest + pytest-mock:
def test_find_foo(mocker):
mocker.patch('shutil.which', return_value = '/path/foo.exe')
foo_path = find_foo()
assert foo_path is '/path/foo.exe'
This way of mocking with pytest-mock doesn't work. shutil.which is still called instead of the mock.
I tried to directly use the mock package which is now part of Python3:
def test_find_foo():
with unittest.mock.patch('shutil.which') as patched_which:
patched_which.return_value = '/path/foo.exe'
foo_path = find_foo()
assert foo_path is '/path/foo.exe'
Sadly the result is the same. Also shutil.which() is called instead of specified mock.
Which steps of successfully implementing a mock are wrong or missed in my test cases?

I investigated more time studying unittest.mock and pytest-mock. I found a simple solution without modifying the production code using the patch decorator. In the following I pasted a code snippet demonstrating a third approach with pytest-mock:
def test_find_foo(mocker):
mocker.patch('__main__.which', return_value='/path/foo.exe')
foo_path = find_foo()
assert foo_path == Path('/path/foo.exe')
Without pytest-mock (plain unittest-mock and a #patch decorator) this solution is also working. The important line in the code snippet above is
mocker.patch('__main__.which', return_value='/path/foo.exe')
The patch decorator expects the name (full path) of the function which will be called from the system under test. This is clearly explained in the mock documentation. The following paragraph summarizes this principle of the patch decorator:
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.

Try using monkeypatch. You can see in the examples how they "monkeypatch" os.getcwd to return the wanted path. In your case I think that this should work:
monkeypatch.setattr("shutil.which", lambda: "/path/foo.exe")

Injecting the which method into your method or object would allow you to mock the dependency without pytest-mock.
def _find_foo(self, which_fn=shutil.which) -> Path:
foo_exe = which_fn('foo', path=None)
if foo_exe:
return Path(foo_exe)
else:
return None
def test_find_foo():
mock_which = Mock(return_value = '/path/foo.exe')
foo_path = obj._find_foo(which_fn=mock_which)
assert foo_path is '/path/foo.exe'

Related

pytest mocker.patch.object's return_value uses different mock than the one I passed it

I'm using pytest to patch the os.makedirs method for a test. In a particular test I wanted to add a side effect of an exception.
So I import the os object that I've imported in my script under test, patch it, and then set the side effect in my test:
from infrastructure.scripts.src.server_administrator import os
def mock_makedirs(self, mocker):
mock = MagicMock()
mocker.patch.object(os, "makedirs", return_value=mock)
return mock
def test_if_directory_exist_exception_is_not_raised(self, administrator, mock_makedirs):
mock_makedirs.side_effect = Exception("Directory already exists.")
with pytest.raises(Exception) as exception:
administrator.initialize_server()
assert exception.value == "Directory already exists."
The problem I ran into was that when the mock gets called in my script under test, the side effect no longer existed. While troubleshooting I stopped the tests in the debugger to look at the ID values for the mock I created and the mock that the patch should have set as the return value and found that they are different instances:
I'm still relatively new to some of the testing tools in python, so this may be me missing something in the documentation, but shouldn't the returned mock patched in here be the mock I created?? Am I patching it wrong?
UPDATE
I even adjusted the import style to grab makedirs directly to patch it:
def mock_makedirs(self, mocker):
mock = MagicMock()
mocker.patch("infrastructure.scripts.src.server_administrator.makedirs", return_value=mock)
return mock
And I still run into the same "different mocks" issue.
:facepalm:
I was patching incorrectly. I'm considering just deleting the whole question/answer, but I figured I'd leave it here in case someone runs into the same situation.
I'm defining the patch like this:
mocker.patch.object(os, "makedirs", return_value=mock)
Which would be a valid structure if I was patching the result a function/method. That is, what this patch is saying is "when you call the makedirs, return this.
What I actually want to do is return a mock in place of the method. In it's current form it makes sense that I see two different mocks because the patch logic is currently "replace makedirs with a new mock and then when that mock is called, return this other mock (the mock I made)"
What I really want is just:
mocker.patch.object(os, "makedirs", mock)
Where my third argument (in the patch.object form) is the mock module parameter (vs the named return_value parameter).
In retrospect, it's pretty obvious when I think about it which is why I'm considering deleting the question, but it's an easy enough trip-up that I'm going to leave it live for now.

Unit test function without return value

I have one function like this one:
def function(df, path_write):
df['A'] = df['col1'] * df['col2']
write(df, path)
The function is not that simple but the question is, how can i make a unit test if the function do not return any value??
If the function returns the new df it's simple, just make:
assert_frame_equal from the library from pandas.testing import assert_frame_equal and mock the write method.
But without that return, how can i test the df line??
In general, I can think of only two kinds of functions: Those that return a value and those that produce side-effects.
With the second kind, you typically don't want the side-effects to actually happen during testing. For example, if your function writes something to disk or sends some data to some API on the internet, you usually don't want it to actually do that during the test, you only want to ensure that it attempts to do the right thing.
To roll with the example of disk I/O as a side-effect: You would usually have some function that does the actual writing to the filesystem that the function under testing calls. Let's say it is named write. The typical apporach would be to mock that write function in your test. Then you would need to verify that that mocked write was called with the arguments you expected.
Say you have the following code.py for example:
def write(thing: object, path: str) -> None:
print("Some side effect like disk I/O...")
def function(thing: object, file_name: str) -> None:
...
directory = "/tmp/"
write(thing, path=directory + file_name)
To test function, I would suggest the following test.py:
from unittest import TestCase
from unittest.mock import MagicMock, patch
from . import code
class MyTestCase(TestCase):
#patch.object(code, "write")
def test_function(self, mock_write: MagicMock) -> None:
test_thing = object()
test_file_name = "test.txt"
self.assertIsNone(code.function(test_thing, test_file_name))
mock_write.assert_called_once_with(
test_thing,
path="/tmp/" + test_file_name,
)
Check out unittest.mock for more details on mocking with the standard library. I would strongly advise to use the tools there and not do custom monkey-patching. The latter is certainly possible, but always carries the risk that you forget to revert the patched objects back to their original state after every test. That can break the entire rest of your test cases and depending on how you monkey-patched, the source of the resulting errors may become very hard to track down.
Hope this helps.
In the mock for write() you can add assert statements to ensure the form of df is as you would expect. For example:
def _mock_write(df, path):
assert path == '<expected path value>'
assert_frame_equal(df, <expected dataframe>)
So the full test case would be:
def test_function(self, monkeypatch):
# Define mock function
def _mock_write(df, path):
assert path == '<expected path value>'
assert_frame_equal(df, <expected dataframe>)
# Mock write function
monkepyatch.setattr(<MyClass>, 'write', _mock_write)
# Run function to enter mocked write function
function(test_df, test_path_write)
N.B. This is assuming you are using pytest as your test runner which supports the set up and tear down of monkeypatch. Other answers show the usage for the standard unittest framework.

Understanding python mock framework

I haven't been able to found a good explanation of this in the net, I'm guessing that i'm missing something trivial but I haven't been able to find it, so I came here to ask the experts :)
I have a test were I need to patch a constructor call, reading the docs as I understand, something like this should work, but:
import unittest.mock as mocker
import some_module
mock_class1 = mocker.patch('some_module.some_class')
print(mock_class1 is some_module.some_class) # Returns False
print(mock_class1) # <unittest.mock._patch>
mock_instance1 = mock_class1.return_value # _patch object has no attr return_value
Instead I get a different output if I do this
with mocker.patch('some_module.some_class') as mock_class2:
print(mock_class2 is some_module.some_class) # Returns True
print(mock_class2) # <MagicMock name=...>
mock_instance2 = mock_class2.return_value # No problem
print(mock_instance2) # <NonCallableMagicMock name=...>
Now, for the test itself, i'm using pytest-mock module which gives a mocker fixture that behaves like the first code block.
I would like to know:
why the behavior differs depending on the way that one call the mock framework
is there a clean way to trigger the behavior of the second code block without the with clause?
1) pytest mocker plugin is being developer to avoid use of context managers; and probably not everybody fond of the way the standard mock plays with functions parameter 2) not really. It is intended to be used either as content manager or function decorator.
I think it is possible use mocker package without pytest
References
https://github.com/pytest-dev/pytest-mock
https://www.packtpub.com/mapt/book/application_development/9781847198846/5/ch05lvl1sec45/integrating-with-python-mocker
What about installing pytest-mock and creating a test like this
import itertools
def test1(mocker):
mock_class1 = mocker.patch('itertools.count')
print(mock_class1 is itertools.count)
print(mock_class1)
mock_instance1 = mock_class1.return_value # Magic staff...
or may be using monkeypatching? Just do not use the standard unittest.mock with pytest

Unable to mock a function in Python (Django)

I'm trying to mock a function used within the function I'm testing, but for some reason I'm not seeing the original function always runs, not the one I created as a mock.
The code I'm testing has this type of setup:
def function_being_tested(foo):
...
function_i_want_to_mock(bar)
...
def function_i_want_to_mock(bar)
print("Inside original function")
...
I've installed Mock and I've tried using unittest.mock patch
Currently the test file uses this setup:
import mock
from django.test import TestCase
def mock_function_i_want_to_mock(bar):
print(bar)
return True
class SupportFunctionsTestCases(TestCase):
#mock.patch("path.to.function.function_i_want_to_mock", mock_function_i_want_to_mock)
def test_function_being_tested(self):
# setup
result = function_being_tested(test_foo)
self.assertEqual(result, expected_result)
What then happens is when I run the test, I always get: "Inside original function", not the parameter printed so it's always running the original function.
I've used this exact setup before and it has worked so I'm not sure what's causing this. Probably some wrong setup...
If anyone has a different way of doing this or spots some error, it would be appreciated.
"path.to.function.function_i_want_to_mock" should be a path to where the function is used, not defined.
So if function_i_want_to_mock is defined in moduleA.py but imported and used in moduleB.py which you are testing then you should use #mock.patch("path.to.moduleB.function_i_want_to_mock", ...).

Unit Testing/Mocking in Python

So let's say I have this bit of code:
import coolObject
def doSomething():
x = coolObject()
x.coolOperation()
Now it's a simple enough method, and as you can see we are using an external library(coolObject).
In unit tests, I have to create a mock of this object that roughly replicates it. Let's call this mock object coolMock.
My question is how would I tell the code when to use coolMock or coolObject? I've looked it up online, and a few people have suggested dependency injection, but I'm not sure I understand it correctly.
Thanks in advance!
def doSomething(cool_object=None):
cool_object = cool_object or coolObject()
...
In you test:
def test_do_something(self):
cool_mock = mock.create_autospec(coolObject, ...)
cool_mock.coolOperation.side_effect = ...
doSomthing(cool_object=cool_mock)
...
self.assertEqual(cool_mock.coolOperation.call_count, ...)
As Dan's answer says, one option is to use dependency injection: have the function accept an optional argument, if it's not passed in use the default class, so that a test can pass in a moc.
Another option is to use the mock library (here or here) to replace your coolObject.
Let's say you have a foo.py that looks like
from somewhere.else import coolObject
def doSomething():
x = coolObject()
x.coolOperation()
In your test_foo.py you can do:
import mock
def test_thing():
path = 'foo.coolObject' # The fully-qualified path to the module, class, function, whatever you want to mock.
with mock.patch('foo.coolObject') as m:
doSomething()
# Whatever you want to assert here.
assert m.called
The path you use can include properties on objects, e.g. module1.module2.MyClass.my_class_method. A big gotcha is that you need to mock the object in the module being tested, not where it is defined. In the example above, that means using a path of foo.coolObject and not somwhere.else.coolObject.

Categories

Resources