pytest fixture is always returning a function - python

I want to be able to return a value from a fixture to multiple tests/test classes, but the value that gets passed is a function.
Here's my code:
import pytest
#pytest.fixture()
def user_setup():
user = {
'name': 'chad',
'id': 1
}
return user
#pytest.mark.usefixtures('user_setup')
class TestThings:
def test_user(self):
assert user_setup['name'] == 'chad'
The output is:
=================================== FAILURES ===================================
_____________________________ TestThings.test_user _____________________________
self = <tests.test_again.TestThings instance at 0x10aed6998>
def test_user(self):
> assert user_setup['name'] == 'chad'
E TypeError: 'function' object has no attribute '__getitem__'
tests/test_again.py:14: TypeError
=========================== 1 failed in 0.02 seconds ===========================
But if I rewrite my test so that it doesn't use the usefixtures decorator, it works as expected:
def test_user(user_setup):
assert user_setup['name'] == 'chad'
Any ideas why it's not working when I try to use the decorator method?

When you use the #pytest.mark.usefixtures marker you still need to provide a similarly named input argument if you want that fixture to be injected in to your test function.
As described in the py.test docs for fixtures:
The name of the fixture function can later be referenced to cause its
invocation ahead of running tests... Test functions can directly use
fixture names as input arguments in which case the fixture instance
returned from the fixture function will be injected.
So just using the #pytest.mark.usefixtures decorator will only invoke the function. Providing an input argument will give you the result of that function.
You only really need to use #pytest.mark.usefixtures when you want to invoke a fixture but don't want to have it as an input argument to your test. As described in the py.test docs.
The reason you are getting an exception that talks about user_setup being a function is because inside your test_user function the name user_setup actually refers to the function you defined earlier in the file. To get your code to work as you expect you would need to add an argument to the test_user function:
#pytest.mark.usefixtures('user_setup')
class TestThings:
def test_user(self, user_setup):
assert user_setup['name'] == 'chad'
Now from the perspective of the test_user function the name user_setup will refer to the function argument which will be the returned value of the fixture as injected by py.test.
But really you just don't need to use the #pytest.mark.usefixtures decorator at all.

In both cases, in the global scope user_setup refers to the function. The difference is, in your nonfixture version, you are creating a parameter with the same name, which is a classic recipe for confusion.
In that nonfixture version, within in the scope of test_user, your user_setup identifier refers to whatever it is you are passing it, NOT the function in the global scope.
I think you probably mean to be calling user_setup and subscripting the result like
assert user_setup()['name'] == 'chad'

Related

How does a pytest mocker work given there is no import statement for it?

I am following this mini-tutorial/blog on pytest-mock. I can not understand how the mocker is working since there is no import for it - in particular the function declaration def test_mocking_constant_a(mocker):
import mock_examples.functions
from mock_examples.functions import double
def test_mocking_constant_a(mocker):
mocker.patch.object(mock_examples.functions, 'CONSTANT_A', 2)
expected = 4
actual = double() # now it returns 4, not 2
assert expected == actual
Somehow the mocker has the attributes/functions of pytest-mocker.mocker: in particular mocker.patch.object . But how can that be without the import statement?
The mocker variable is a Pytest fixture. Rather than using imports, fixtures are supplied using dependency injection - that is, Pytest takes care of creating the mocker object for you and supplies it to the test function when it runs the test.
Pytest-mock defines the "mocker" fixture here, using the Pytest fixture decorator. Here, the fixture decorator is used as a regular function, which is a slightly unusual way of doing it. A more typical way of using the fixture decorator would look something like this:
#pytest.fixture()
def mocker(pytestconfig: Any) -> Generator[MockerFixture, None, None]:
"""
Return an object that has the same interface to the `mock` module, but
takes care of automatically undoing all patches after each test method.
"""
result = MockerFixture(pytestconfig)
yield result
result.stopall()
The fixture decorator registers the "mocker" function with Pytest, and when Pytest runs a test with a parameter called "mocker", it inserts the result of the "mocker" function for you.
Pytest can do this because it uses Python's introspection features to view the list of arguments, complete with names, before calling the test function. It compares the names of the arguments with names of fixtures that have been registered, and if the names match, it supplies the corresponding object to that parameter of the test function.

pytest access parameters from function scope fixture

Let's assume I have the following code:
#pytest.mark.parametrize("argument", [1])
def test_func(self, function_context, argument)
And I have the following function scope fixture:
#pytest.fixture(scope='function')
def function_context(session_context):
# .... do something ....
Is it possible to access the current function argument from within the function_context fixture?
In my case - I want to get the value 1 that is being passed in parametrize from within function_context.
Fixtures in pytest are instantiated before the actual tests are ran, so it shouldn't be possible to access the test function argument at the fixture definition stage. However, I can think of two ways to bypass this:
1. Monkeypatching
You can monkeypatch the fixture, i.e. temporarily change some of its attributes, based on the parameter of the function that uses this fixture. For example:
#pytest.fixture(scope='function')
def function_context(session_context):
# .... do something ....
#pytest.mark.parametrize("argument", [1])
def test_func(self, function_context, argument, monkeypatch):
monkeypatch.setattr(function_context, "number", argument) # assuming you want to change the attribute "number" of the function context
# .... do something ....
Although your fixture is valid for the scope of the function only anyhow, monkeypatching is also only valid for a single run of the test.
2. Parametrizing the fixture instead of the test function
Alternatively, you can also choose to parametrize the fixture itself instead of the test_func. For example:
#pytest.fixture(scope='function', params=[0, 1])
def function_context(session_context, request):
param = requests.param # now you can use param in the fixture
# .... do something ...
def test_func(self, function_context):
# .... do something ...

How do I mock the default arguments of a function that is used by the function I'm testing?

I have this my_module.py:
def _sub_function(do_the_thing=True):
if do_the_thing:
do_stuff()
else:
do_something_else()
def main_function():
# do some stuff
if some_condition:
return _sub_function()
else:
return _sub_function(do_the_thing=False)
then I have this test, test_my_module.py:
import unittest
from unittest import mock
import my_module
class TestMyModule(unittest.TestCase):
#mock.patch.object("my_module._sub_function", "__defaults__", (False,))
def test_main_function(self):
print(my_module.main_function())
if __name__ == "__main__":
unittest.main()
I have a function _sub_function that takes a default argument that decides if it performs some steps or not. Normally, main_function calculates when those actions need to be performed and overrides that default argument. Unfortunately, when running tests I can't perform those actions when I normally need to.
So my idea was to use a default argument on _sub_function and in my test to patch the function to monkey-patch that argument to be False so that it skips these actions during testing. Unfortunately I can't use the code in this question because I'm testing main_function, and not _sub_function, so I don't have _sub_function in my test. mock.patch.object can only take the object being patched as an argument, not a string containing the import path of the object (like mock.patch does), so the above code doesn't work, it raises an AttributeError: my_module._sub_function does not have the attribute '__defaults__' on the mock.patch.object() line.
Is there a way to patch a functions default arguments using the string import path of that function.
Or is there a better way to achieve what I want?
The only problem is you are trying to patch an attribute of a str object, not your function:
class TestMyModule(unittest.TestCase):
#mock.patch.object(my_module._sub_function, "__defaults__", (False,))
def test_main_function(self):
print(my_module.main_function())
The AttributeError being raised doesn't make that clear, unfortunately.

Custom exceptions in unittests

I have created my custom exceptions as such within errors.py
mapper = {
'E101':
'There is no data at all for these constraints',
'E102':
'There is no data for these constraints in this market, try changing market',
'E103':
'There is no data for these constraints during these dates, try changing dates',
}
class DataException(Exception):
def __init__(self, code):
super().__init__()
self.msg = mapper[code]
def __str__(self):
return self.msg
Another function somewhere else in the code raises different instances of DataException if there is not enough data in a pandas dataframe. I want to use unittest to ensure that it returns the appropriate exception with its corresponding message.
Using a simple example, why does this not work:
from .. import DataException
def foobar():
raise DataException('E101')
import unittest
with unittest.TestCase.assertRaises(DataException):
foobar()
As suggested here: Python assertRaises on user-defined exceptions
I get this error:
TypeError: assertRaises() missing 1 required positional argument: 'expected_exception'
Or alternatively:
def foobar():
raise DataException('E101')
import unittest
unittest.TestCase.assertRaises(DataException, foobar)
results in:
TypeError: assertRaises() arg 1 must be an exception type or tuple of exception types
Why is it not recognizing DataException as an Exception? Why does the linked stackoverflow question answer work without supplying a second argument to assertRaises?
You are trying to use methods of the TestCase class without creating an instance; those methods are not designed to be used in that manner.
unittest.TestCase.assertRaises is an unbound method. You'd use it in a test method on a TestCase class you define:
class DemoTestCase(unittest.TestCase):
def test_foobar(self):
with self.assertRaises(DataException):
foobar()
The error is raised because unbound methods do not get self passed in. Because unittest.TestCase.assertRaises expects both self and a second argument named expected_exception you get an exception as DataException is passed in as the value for self.
You do now have to use a test runner to manage your test cases; add
if __name__ == '__main__':
unittest.main()
at the bottom and run your file as a script. Your test cases are then auto-discovered and executed.
It is technically possible to use the assertions outside such an environment, see Is there a way to use Python unit test assertions outside of a TestCase?, but I recommend you stick to creating test cases instead.
To further verify the codes and message on the raised exception, assign the value returned when entering the context to a new name with with ... as <target>:; the context manager object captures the raised exception so you can make assertions about it:
with self.assertRaises(DataException) as context:
foobar()
self.assertEqual(context.exception.code, 'E101')
self.assertEqual(
context.exception.msg,
'There is no data at all for these constraints')
See the TestCase.assertRaises() documentation.
Last but not least, consider using subclasses of DataException rather than use separate error codes. That way your API users can just catch one of those subclasses to handle a specific error code, rather than having to do additional tests for the code and re-raise if a specific code should not have been handled there.

How to evade non-called fixture in Pytest?

Pytest suite has a brilliant feature of fixtures.
To make a reusable fixture, we mark a function with special decorator:
#pytest.fixture
def fix():
return {...}
It can later be used in our test through an argument name matching the original name of the fixture:
def test_me(fix):
fix['field'] = 'expected'
assert(fix['field'] == 'expected')
Although from time to time we might forget to specify the fixture in the arguments, and, since the name of the factory matches the name of the object produced, the test will silently apply changes to the factory object itself:
def test_me(): # notice no arg
fix['this is'] = 'a hell to debug'
Certainly, the outcome is undesirable. It would be nice, for instance, to be able to add some suffix to factory function, but the pytest.fixture decorator apparently does not have a means to override the name for the fixture.
Any other advice would suffice as well.
What is a recommended technique to protect ourselves from this kind of issue?
You can use autouse=True while defining fixture to invoke the fixture every time the scope of fixture starts. This feature was added in pytest 2.0.
For example:
import pytest
#pytest.fixture(scope='function',autouse=True)
def fixture_a():
return 5
def test_a():
assert fixture_a == 5
As you can see, I did not have to declare fixture as an argument in test_a to access it.
Documentation: https://docs.pytest.org/en/latest/reference.html#pytest-fixture
Code example: https://docs.pytest.org/en/latest/fixture.html#autouse-fixtures-xunit-setup-on-steroids

Categories