Mock method that is imported into the module under test - python

Say I want to test this module:
import osutils
def check_ip6(xml):
ib_output = osutils.call('iconfig ib0')
# process and validate ib_output (to be unit tested)
...
This method is dependent on the environment, because it makes a System call (which expects a specific network interface), so its not callable on a testmachine.
I want to write a Unit test for that method which checks if the processing of ib_output works as expected. Therefore I want to mock osutils.call and let it just return testdata. What is the preferred way to do that? Do I have to do mocking or (monkey) patching?
Example test:
def test_ib6_check():
from migration import check_ib6
# how to mock os_utils.call used by the check_ib6-method?
assert check_ib6(test_xml) == True

One solution would be to do from osutils import call and then when patching things replace yourmodule.call with something else before calling test_ib6_check.

Ok, I found out that this has nothing to do with mocks, afaik I just need a monkey patch: I need to import and change the osutils.call-method and then import the method under test (and NOT the whole module, since it would then import the original call-method) afterwards. So this method will then use my changed call-method:
def test_ib6_check():
def call_mock(cmd):
return "testdata"
osutils.call = call_mock
from migration import check_ib6
# the check_ib6 now uses the mocked method
assert check_ib6(test_xml) == True

Related

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.

Using patch() to mock something that I don't explicilty import

Let's say I have the following modules:
# src/myapp/utils.py
import thirdparty
from myapp.secrets import get_super_secret_stuff
def get_thirdparty_client() -> return thirdparty.Client:
thirdparty.Client(**get_super_secret_stuff())
# src/myapp/things.py
from myapp.utils import get_thirdparty_client
from myapp.transformations import apply_fairydust, make_it_sparkle
def do_something(x):
thirdparty_client = get_thirdparty_client()
y = thidparty_client.query(apply_fairydust(x))
return make_it_sparkle(y)
Assume that myapp is lightly-tested legacy code, and refactoring is out of the question. Also assume (annoyingly) that thirdparty.Client does non-deterministic network I/O in its __init__ method. Therefore I intend to mock the thirdparty.Client class itself so as to make this do_something function testable.
Assume also that I must use unittest and cannot use another test framework like Pytest.
It seems like the patch function from unittest.mock is the right tool for the job. However, I'm unsure of how to apply the usual admonition to "patch where it is used."
Ideally I want to write a test that looks something like this:
# tests/test_myapp/test_things.py
from unittest import TestCase
from unittest.mock import patch
from myapp.things import do_something
def gen_test_pairs():
# Generate pairs of expected inputs and outputs
...
class ThingTest(unittest.TestCase):
#patch('????')
def test_do_something(self, mock_thirdparty_client):
for x, y in gen_test_pairs:
with self.subTest(params={'x': x, 'y': y}):
mock_thirdparty_client.query.return_value = y
self.assertEqual(do_something(x), y)
My problem is that I don't know what to write in place of the ????, because I never actually import thirdparty.Client in src/myapp/things.py.
Options I considered:
Apply the patch at myapp.utils.thirdparty.Client, which makes my test fragile and dependent on implementation details.
"Break the rules" and apply the patch at thirdparty.Client.
Import get_thirdparty_client in the test, use patch.object on it, and set its return_value to another MagicMock that I create separately, and this second mock would stand in for thirdparty.Client. This makes for more verbose testing code that can't easily be applied as a single decorator.
None of these options sounds particularly appealing, but I don't know which is considered the least bad.
Or is there another option available to me that I am not seeing?
The correct answer is 2: apply the patch to thirdparty.Client.
This is correct because it does NOT in fact break the "patch where it is used" rule.
The rule is intended to be descriptive, not literal. In this case, thirdparty.Client is considered to be "used" in the thirdparty module.
This concept is described in more detail in the 2018 PyCon talk "Demystifying the Patch Function" by Lisa Roach. The full recording is available on YouTube here: https://youtu.be/ww1UsGZV8fQ?t=537. The explanation of this particular case starts at approximately 9 minutes into the video.

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

Python Mock Patch patches a used module for submodule under test and every other imported submodule in the submodule under test

Sorry for the rather confusing title, but find it really hard to get to a crisp headline.
My Problem:
I have a submodule under test lets call it backend. And I use random.choice in backend. I patch it in my test and that works. It is patched. backend imports pymongoto do some database stuff and pymongo uses random.choiceas well for selecting the right server in core.py.
Somehow my patch of backend.random.choice also is used for backend.pymongo.random.choice. I am not sure why. Is that the correct behavior? And if it is, what is the way to get around that? Preferebly without changing any code in backend and pymongo, but only in my tests.
Other investigation:
I set up a little test construct to see if that is something related to my project or a general thing.
I created some modules:
bar/bar.py
import random
def do_some_other_something():
return random.choice([10])
foo/foo.py
import random
from bar.bar import do_some_other_something
def do_something():
return random.choice([1])
def do_something_else():
return do_some_other_something()
And a test case:
from foo.foo import do_something, do_something_else
from unittest import mock
assert(do_something() == 1) # check
assert(do_something_else() == 10) # check
with mock.patch("foo.foo.random.choice") as mock_random_choice:
assert (do_something() != 1) # check (mocked as expected)
assert (do_something_else() == 10) # assertion! also mocked
So I am really confused about this. I explicitly mocked foo.foo's random.choice not bar.bar's. So why is that happening? Intended? Is there a way to fix that?
Thank you!
There's only one random module in the program. When foo.foo and bar.bar both import random, they share the random module. Patching the choice function on the random module modifies the one random module used by every part of the program, including both foo.foo and bar.bar.
Instead of patching foo.foo.random.choice, patch foo.foo.random. That replaces foo.foo's reference to the random module, but not bar.bar's reference. bar.bar will continue to access the real random module, but foo.foo will see a mock random.

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