I have some code that uses functions as parameters and I've added some logging that includes __qualname__, this caused my unit tests to fail since the Mock object I passed in raises an AttributeError for __qualname__.
mock_func = Mock()
A simple solution to this problem is to manually add the expected attribute to the mock:
mock_func.__qualname__ = "mock_function"
Or add it to the spec of the mock when I create it:
mock_func = Moc(["__qualname__"])
But these solutions are unsatisfying since I would need to change them whenever I use a different built-in attribute (e.g. __name__).
Is there a simple way to create a Mock which behaves like a function?
The closest I found was this bug report that was opened on the wrong repository, and this request which has no replies.
You can simply use any function as spec for the mock.
mock_func = Mock(spec=max)
mock_func.__qualname__
>>> <Mock name='mock.__qualname__' id='140172665218496'>
Related
I am attempting to mock an object to perform some testing
test.py
#patch("api.configuration.client.get_configuration")
def test(mock_client_get_configuration):
mock_client_get_configuration.return_value = "123"
result = code_under_test()
assert result
Inside code_under_test() I make the concrete call to get_configuration().
code.py
from api.configuration.client import get_configuration
def code_under_test():
config = get_configuration("a", "b")
However, whenever I run my test, get_configuration is always the concrete version and not my mock.
If I add a print in my test, I can see that mock_client_get_configuration is a MagicMock.
If I add a print inside code_under_test, then get_configuration is always the actual <function.... and not my mock.
I feel somewhere I am going wrong in how I create my mock. Perhaps it is because my mock does not mimic the two parameters needed on the concrete call so the signature is incorrect?
I'm using Python 3.9 and pytest 7.0.1.
Below I have added some dummy code which exactly represent what I am trying to do.
I am importing a function from a class with an alias, I am doing this since the class is running the aliased version itself.
Things I have tried:
http://bhfsteve.blogspot.com/2012/06/patching-tip-using-mocks-in-python-unit.html This does not give a specific solution to the problem.
patched a.dummy.class.function.alias as a.dummy.class.function this allows execution but will not run the alias_function_mocker() as in the class the function is called as alias() and not function()
I tried running as a.dummy.class.function.alias but this results in an attribute error as alias isn't actually an attribute of class(because function() is)
from a.dummy.class import function as alias
def alias_function_mocker():
return 0
#patch("a.dummy.class.function.alias", side_effect=alias_function_mocker):
def test_function(mocked_function):
function_calling_alias()
return 0
What I think the problem roots from is that the class file is calling function using alias and there doesn't seem to be a way to patch aliased function calls. The simple and most naive solution I can think of is just do not use an alias.
You need to mock the object where it is. If you imported the module/function to file.py, you need to mock the object of this file.
If you are using the test in the same file of the method, this could be the solution:
from os.path import isdir as is_it_a_dir
from unittest.mock import patch
def check(path):
return is_it_a_dir(path)
with patch('__main__.is_it_a_dir') as mock_isitadir:
mock_isitadir.return_value = True
assert check('/any/invalid/path') == True
If your test is in another file, than your approch should work.
Just to complement Mauro Baraldi's answer, this is how to do it using decorators.
#patch("__main__.alias")
I'm trying to create some unit tests for some code here at work.
The code takes in an object and based on information within that object imports a specific module then creates an instance of it.
The test I am trying to write creates the object and then I check that it is an instance of the class I expect it to import. The issue is the isinstance check is failing.
Here is what my test looks like.
import importlib
from path.to.imported_api import SomeApi
api = importlib.import_module("path.to.imported_api").create_instance() # create_instance() is a function that returns SomeApi().
assert isinstance(api, SomeApi) # This returns false, but I am not sure why.
The reason for the difference is, that whereas both objects refer to the same module, they get different identifiers as you load a new module and bypass sys.modules. See also the explanation here: https://bugs.python.org/issue40427
A hack might be to compare the name:
assert isinstance(api.__class__.__name__, SomeApi.__name__)
There are a few things that could cause that:
So first, it could be that the api is just returning something that looks like SomeApi(). Also it coud is be that SomeApi is overwriting isinstance behaviour.
Is it possible to mock methods of imported modules with unittest.mock in Python 3.5?
# file my_function.py
import os
my_function():
# do something with os, e.g call os.listdir
return os.listdir(os.getcwd())
# file test_my_function.py
test_my_function():
os = MagickMock()
os.listdir = MagickMock(side_effect=["a", "b"])
self.assertEqual("a", my_function())
I expected that the the os.listdir method returns the specified side_effect "a" on the first call, but inside of my_function the unpatched os.listdir is called.
unittest.mock have two main duties:
Define Mock objects: object designed to follow your screenplay and record every access to your mocked object
patching references and recover the original state
In your example, you need both functionalities: Patching os.listdir reference used in production code by a mock where you can have complete control of how it will respond. There are a lot of ways to use patch, some details to take care on how use it and cavelets to know.
In your case you need to test my_function() behaviour and you need to patch both os.listdir() and os.getcwd(). Moreover what you need is control the return_value (take a look to the pointed documentation for return_value and side_effect differences).
I rewrote your example a little bit to make it more complete and clear:
my_function(nr=0):
l = os.listdir(os.getcwd())
l.sort()
return l[nr]
#patch("os.getcwd")
#patch("os.listdir", return_value=["a","b"])
def test_my_function(mock_ld, mock_g):
self.assertEqual("a", my_function(0))
mock_ld.assert_called_with(mock_g.return_value)
self.assertEqual("a", my_function())
self.assertEqual("b", my_function(1))
with self.assertRaises(IndexError):
my_function(3)
I used the decorator syntax because I consider it the cleaner way to do it; moreover, to avoid the introduction of too many details I didn't use autospecing
that I consider a very best practice.
Last note: mocking is a powerful tool but use it and not abuse of it, patch just you need to patch and nothing more.
What misspellings / typos are supported in Python?
Not alternate spellings such as is_dir vs isdir, nor color vs colour but actual wrongly spelt aliases, such as proprety for property (which isn't supported).
As of Python 3.5 beta 3 the unittest.mock object now supports assret standing in for assert -- note that this is not the keyword assert, but any attribute of a mock object that matches the regular expression assert.* or assret.*.
Some explanation:
When a mock object is created the default for any attribute access is to return a new Mock, except in one case: if the attribute is one of assert_called_with, assert_called_once_with, assert_any_call, assert_has_calls, and assert_not_called, in which case some code is actually run.
The problem is if one forgets the exact name and uses, for example, assert_called, then instead of code running to check that the mock was called, a new mock is returned instead and the test one wrote passes instead of actually doing the test and possibly failing.
To combat this problem Mock now raises an AttributeError if any access is made to an attribute that starts with assert.
Besides assert, Mock will also raise an AttributeError if any access is made to an attribute that starts with assret.
If one does not want the extra protection (for assert and assret) one can use unsafe=True when creating the Mock.