Any way to pass parameters into pytest fixture? - python

I am not talking about the Parameterizing a fixture feature that allows a fixture to be run multiple times for a hard-coded set of parameters.
I have a LOT of tests that follow a pattern like:
httpcode = 401 # this is different per call
message = 'some message' # this is different per call
url = 'some url' # this is different per call
mock_req = mock.MagicMock(spec_set=urllib2.Request)
with mock.patch('package.module.urllib2.urlopen', autospec=True) as mock_urlopen, \
mock.patch('package.module.urllib2.Request', autospec=True) as mock_request:
mock_request.return_value = mock_req
mock_urlopen.side_effect = urllib2.HTTPError(url, httpcode, message, {}, None)
connection = MyClass()
with pytest.raises(MyException):
connection.some_function() # this changes
Essentially, I have a class that's an API client, and includes custom, meaningful exceptions that wrap urllib2 errors in something API-specific. So, I have this one pattern - patching some methods, and setting side effects on one of them. I use it in probably a dozen different tests, and the only differences are the three variables which are used in part of the side_effect, and the method of MyClass() that I call.
Is there any way to make this a pytest fixture and pass in these variables?

You can use indirect fixture parametrization
http://pytest.org/latest/example/parametrize.html#deferring-the-setup-of-parametrized-resources
#pytest.fixture()
def your_fixture(request):
httpcode, message, url = request.param
mock_req = mock.MagicMock(spec_set=urllib2.Request)
with mock.patch('package.module.urllib2.urlopen', autospec=True) as mock_urlopen, \
mock.patch('package.module.urllib2.Request', autospec=True) as mock_request:
mock_request.return_value = mock_req
mock_urlopen.side_effect = urllib2.HTTPError(url, httpcode, message, {}, None)
connection = MyClass()
with pytest.raises(MyException):
connection.some_function() # this changes
#pytest.mark.parametrize('your_fixture', [
(403, 'some message', 'some url')
], indirect=True)
def test(your_fixture):
...
and your_fixture will run before test with desired params

I've done a bunch more research on this since posting my question, and the best I can come up with is:
Fixtures don't work this way. Just use a regular function, i.e.:
def my_fixture(httpcode, message, url):
mock_req = mock.MagicMock(spec_set=urllib2.Request)
with mock.patch('package.module.urllib2.urlopen', autospec=True) as mock_urlopen, \
mock.patch('package.module.urllib2.Request', autospec=True) as mock_request:
mock_request.return_value = mock_req
mock_urlopen.side_effect = urllib2.HTTPError(url, httpcode, message, {}, None)
connection = MyClass()
return (connection, mock_request, mock_urlopen)
def test_something():
connection, mock_req, mock_urlopen = my_fixture(401, 'some message', 'some url')
with pytest.raises(MyException):
connection.some_function() # this changes

How to pass parameters into a fixture?
Unpack that idea for a moment: you're asking for a fixture, which is a function, which reacts to parameters. So, return a function, which reacts to parameters:
#pytest.fixture
def get_named_service():
def _get_named_service(name):
result = do_something_with_name(name)
return result
return _get_named_service
Thus, in the test, you can provide the parameters to the function:
def test_stuff(get_named_service):
awesome_service = get_named_service('awesome')
terrible_service = get_named_service('terrible')
# Now you can do stuff with both services.
This is documented as a factory pattern:
https://docs.pytest.org/en/latest/how-to/fixtures.html#factories-as-fixtures
Which, as the OP found, is just a function, but with the advantage of being inside the conftest where all the other common utils and setup/teardown code resides; plus self-documenting the dependencies of the test.

I know this is old, but maybe it helps someone who stumbles on this again
#pytest.fixture
def data_patcher(request):
def get_output_test_data(filename, as_of_date=None):
# a bunch of stuff to configure output
return output
def teardown():
pass
request.addfinalizer(teardown)
return get_output_test_data
and then, inside the function:
with patch('function to patch', new=data_patcher):

Some trick with pytest.mark and we have a fixture with arguments.
from allure import attach
from pytest import fixture, mark
def get_precondition_params(request_fixture, fixture_function_name: str):
precondition_params = request_fixture.keywords.get("preconditions_params")
result = precondition_params.args[0].pop(fixture_function_name) if precondition_params is not None else None
return result
#fixture(scope="function")
def setup_fixture_1(request):
params = get_precondition_params(request, "setup_fixture_1")
return params
#mark.preconditions_params(
{
"setup_fixture_1": {
"param_1": "param_1 value for setup_fixture_1",
"param_2": "param_2 value for setup_fixture_1"
},
}
)
def test_function(setup_fixture_1):
attach(str(setup_fixture_1), "setup_fixture_1 value")
Now we can use one fixture code, parametrize it with mark, and do anything with params inside fixture. And fixture will be executed as precondition (how it must be), not as step (like it will be if we return function from fixture).

Related

Pytest - mocking a side_effect on mock's nested attribute function / method

I have a fixture mocking an external library like so, using pytest-mock, which is a wrapper around unittest.mock.
# client.py
import Test as TestLibrary
class LibraryName():
def get_client():
return TestLibrary.Library()
# library_service.py
def using_library():
'''
Edited note: Library().attribute behind the scenes is set to
self.attribute = Attribute()
so this may be affecting the mocking
'''
client = LibraryName.get_client()
return client.attribute.method()
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
# test_library_service.py
def test_library_method(library_client_mock):
result = using_library()
I can mock a return value like so:
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
result = using_library()
assert result == "test"
but I can't mock throwing an Exception with side_effect
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError # doesn't work
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError() # doesn't work
attrs = { 'attribute.method.side_effect': TypeError }
library_client_mock.configure_mock(**attrs) # doesn't work
with pytest.raises(TypeError):
using_library() # fails assertion
what I missing here?
These are the errors in your code:
Change:
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
To:
library_client_mock.return_value.attribute.method.return_value = "test"
Change:
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError
To:
library_client_mock.return_value.attribute.method.side_effect = TypeError
Explanation
The .return_value must only be used for callable objects e.g. a function as documented:
return_value
Set this to configure the value returned by calling the mock:
>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'
Thus, you can use .return_value only for the following:
TestLibrary.Library()
TestLibrary.Library().attribute.method()
But not for:
TestLibrary.Library().attribute
Because .attribute is not a callable e.g. TestLibrary.Library().attribute().
Warning
The way you are patching Library is via its source location at Test.Library (or aliased as TestLibrary.Library). specifically via:
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
It works currently because the way you import and use it is via the root path.
# client.py
import Test as TestLibrary
...
return TestLibrary.Library()
...
But if we change the way we imported that library and imported a local version to client.py:
# client.py
from Test import Library # Instead of <import Test as TestLibrary>
...
return Library() # Instead of <TestLibrary.Library()>
...
It will now fail. Ideally, you should patch the specific name that is used by the system under test, which here is client.Library.
import client
return mocker.patch.object(client, 'Library')
Unless you are sure that all files that will use the library will import only the root and not a local version.
#Niel Godfrey Ponciano set me on the right path with this syntax for the side_effect
library_client_mock.return_value.attribute.method.side_effect = TypeError
but it wasn't enough.
In
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
I had to add an extra mock:
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
mock_library_client = mocker.patch.object(TestLibrary, 'Library')
# option 1
mock_attribute = Mock()
# option 2, path to Library.attribute = Attribute()
mock_attribute = mocker.patch.object(TestLibrary.services, 'Attribute', autospec=True)
mock_library_client.attach_mock(mock_attribute, "attribute")
return mock_library_client
and then both of the following statements worked as expected. Although I am not sure why return_value works out of the box without an attached mock, but side_effect does not.
# return_value set correctly
# NOTE return_value needed after each
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
# side_effect set correctly
# NOTE return_value not needed after "attribute"
library_client_mock.return_value.attribute.method.side_effect = TypeError

How to mock out custom throttle class for testing in django-rest-framework?

I have a custom throttle class like: (print statements are for debugging :)) in api.throttle.py
print("befor CustomThrottle class")
class CustomThrottle(BaseThrottle):
def __init__(self):
super().__init__()
print("initializing CustomThrottle", self)
self._wait = 0
def allow_request(self, request, view):
print("CustomThrottle.allow_request")
if request.method in SAFE_METHODS:
return True
# some checking here
if wait > 0:
self._wait = wait
return False
return True
def wait(self):
return self._wait
my api.views.py is like:
from api.throttle import CustomThrottle
print("views module")
class SomeView(APIView):
print("in view")
throttle_classes = [CustomThrottle]
def post(self, request, should_exist):
# some processing
return Response({"message": "Done."})
and my tests is api/tests/test_views.py:
#patch.object(api.views.CustomThrottle, "allow_request")
def test_can_get_confirmation_code_for_registered_user(self, throttle):
throttle.return_value = True
response = self.client.post(path, data=data)
self.assertEqual(
response.status_code,
status.HTTP_200_OK,
"Should be successful",
)
#patch("api.views.CustomThrottle")
def test_learn(self, throttle):
throttle.return_value.allow_request.return_value = True
response = self.client.post(path, data=data)
self.assertEqual(
response.status_code,
status.HTTP_200_OK,
"Should be successful",
)
the first test passes correctly but still the CustomThrottle class is instantiated but the allow_request method is mocked; the second test shows that neither CustomThrottle class is mocked nor the allow_request method and it fails with status 429 (CustomThrottle has a throttle rate of 2 minutes).
After spending some time and testing different scenarios (Putting debug like print statements here and there :) ) I finally found out why it's not working, But it may not be as correct as I think so any better answer is welcome.
I defined list of throttle classes like throttle_classes = [CustomThrottle] so when server starts it's imported and evaluated so there's a reference to the actual and unmocked version of CustomThrottle in my SomeView class and when processing the responses it will use that for instantiation and so my test fails, but when I patch it like patch.object(CustomThrottle, "allow_request") when view actually needs to check throttles it will call something like CustomThrottle().allow_request(...) (pay attention to parentheses for object creation); the object has not a method like allow_request in itself so it searches that in it's class and use the mocked version correctly.
If you want also to mock CustomThrottle class, and his properties, you also need to patch in places where do you need it, because of patch decorator applies monkey patching in places where you calling it.
Regarding your case, you able to do something like
from unittest.mock import MagicMock, patch
#patch('api.views.CustomThrottle')
#patch('api.views.PhoneConfirmationThrottle')
def test_learn(self, phone_throttle, custom_throttle):
mocked_custom_throttle_instance = MagicMock()
mocked_custom_throttle_instance.allow_request = True
mocked_custom_throttle_instance.status = ...
# Do some stuff which do you need with the mocked class
# and then return an instance for your patched object
custom_throttle.return_value = mocked_custom_throttle_instance
response = self.client.post(path, data=data)
self.assertEqual(
response.status_code,
status.HTTP_200_OK,
"Should be successful",
)
It will replace your real object with the mocked one, also check out unittest.mock documentation, it will help you in the future.

How to create unit test for methods without return statement in Pytest?

I'm trying to do a pytest on a function without return values in a class:
# app.py
from utils import DBConnection
class App:
def add_ticket_watcher(self, ticket_key, watcher_name):
if ticket_key is None:
raise ValueError('Ticket key is empty')
instance = DBConnection()
instance.save(ticket_key, watcher_name)
From above code, add_ticket_watcher() is a method which has no return statement. I learned from this article that we can use mocks to mimic the expected behavior of this method.
For mocking function with pytest, I found that we can use monkeypatch.
So, my approach is to perform 2 test cases for add_ticket_watcher():
test valid ticket key
test invalid ticket key
I have something like:
# test_app.py
import pytest
from src.app import App
#pytest.fixture
def app():
app = App()
return app
# Positive test case: Setup parameterize test for valid ticket key
add_valid_watcher_data = (
('func_name', 'ticket_key', 'watcher_name', 'expected_result', 'comment'),
[
('add_ticket_watcher', 'TIC-13527', 'someone', None, 'add watcher at valid ticket key'),
]
)
#pytest.mark.parametrize(*add_valid_watcher_data)
def test_add_ticket_watcher(monkeypatch, app, func_name, ticket_key, watcher_name, expected_result, comment):
def mock_add_ticket_watcher(ticket_key, watcher_name):
# Since `add_ticket_watcher()` has no return statement, I'm mocking the result as None
return None
monkeypatch.setattr(app, "add_ticket_watcher", mock_add_ticket_watcher)
# Run each input parameter from add_valid_watcher_data to `add_ticket_watcher()`
watcher = getattr(app, func_name)(ticket_key, watcher_name)
# If watcher has None value, then `add_ticket_watcher()` execution is successful.
assert expected_result == watcher
# Negative test case: Setup parameterize test for invalid ticket key
add_invalid_watcher_data = (
('func_name', 'ticket_key', 'watcher_name', 'exception_message', 'comment'),
[
('add_ticket_watcher', 'TIC-xx', 'someone', 'Ticket key is empty', 'add watcher at invalid ticket key'),
('add_ticket_watcher', None, 'someone', 'Ticket key is empty', 'ticket key has None value'),
]
)
#pytest.mark.parametrize(*add_invalid_watcher_data)
def test_exception_add_ticket_watcher(app, func_name, ticket_key, watcher_name, exception_message, comment):
with pytest.raises(ValueError, match=exception_message):
# Run each input parameter from add_invalid_watcher_data to `add_ticket_watcher()`
getattr(app, func_name)(ticket_key, watcher_name)
In test_add_ticket_watcher(), I'm not sure what to assert. However, since App.add_ticket_watcher(ticket_key, watcher_name) has no return statement. I create a mock function to return None.
Is there a better way to achieve the same purpose?
How to create unit test for methods without return statement in Pytest?
In addition to what chepner has mentioned. You can test if logger.info is being called using mock.
And in order to test the negative scenario, you can force it to raise exception using mock side_effects, and then you can test whether logger.exception is getting called.
I am not familiar with pytest but unittest. However, I would write these 3 tests for your function ( with a little modification to your existing function :))
Note: Test method names are descriptive, hence, I am not adding extra comments.
app.py
from utils import DBConnection
import cx_Oracle
class App:
def add_ticket_watcher(self, ticket_key, watcher_name):
if ticket_key is None:
raise ValueError('Ticket key is empty')
instance = DBConnection()
try:
instance.save(ticket_key, watcher_name)
except Exception as e:
raise cx_Oracle.DatabaseError('Database save failed')
test_app.py
import unittest
import app
import cx_Oracle
from mock import patch
class TestApp(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.app = app.App()
def test_if_exception_raised_when_ticket_value_is_none(self):
with self.assertRaises(ValueError):
self.app.add_ticket_watcher(None, 'dummyname')
def test_if_dbconnection_save_is_called(self):
with patch('app.DBConnection.save') as mock_dbconn_save:
self.app.add_ticket_watcher(123, 'dummyname')
self.assertTrue(mock_dbconn_save.called)
def test_if_dbconnection_save_raises_error(self):
with patch('app.DBConnection.save', side_effect = Exception) as mock_dbconn_save_exc:
with self.assertRaises(cx_Oracle.DatabaseError):
self.app.add_ticket_watcher(123, 'dummyname')
if __name__ == '__main__':
unittest.main(verbosity=2)

py.test: how to get the current test's name from the setup method?

I am using py.test and wonder if/how it is possible to retrieve the name of the currently executed test within the setup method that is invoked before running each test. Consider this code:
class TestSomething(object):
def setup(self):
test_name = ...
def teardown(self):
pass
def test_the_power(self):
assert "foo" != "bar"
def test_something_else(self):
assert True
Right before TestSomething.test_the_power becomes executed, I would like to have access to this name in setup as outlined in the code via test_name = ... so that test_name == "TestSomething.test_the_power".
Actually, in setup, I allocate some resource for each test. In the end, looking at the resources that have been created by various unit tests, I would like to be able to see which one was created by which test. Best thing would be to just use the test name upon creation of the resource.
You can also do this using the Request Fixture like this:
def test_name1(request):
testname = request.node.name
assert testname == 'test_name1'
You can also use the PYTEST_CURRENT_TEST environment variable set by pytest for each test case.
PYTEST_CURRENT_TEST environment variable
To get just the test name:
os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0]
The setup and teardown methods seem to be legacy methods for supporting tests written for other frameworks, e.g. nose. The native pytest methods are called setup_method as well as teardown_method which receive the currently executed test method as an argument. Hence, what I want to achieve, can be written like so:
class TestSomething(object):
def setup_method(self, method):
print "\n%s:%s" % (type(self).__name__, method.__name__)
def teardown_method(self, method):
pass
def test_the_power(self):
assert "foo" != "bar"
def test_something_else(self):
assert True
The output of py.test -s then is:
============================= test session starts ==============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
plugins: cov
collected 2 items
test_pytest.py
TestSomething:test_the_power
.
TestSomething:test_something_else
.
=========================== 2 passed in 0.03 seconds ===========================
Short answer:
Use fixture called request
This fixture has the following interesting attributes:
request.node.originalname = the name of the function/method
request.node.name = name of the function/method and ids of the parameters
request.node.nodeid = relative path to the test file, name of the test class (if in a class), name of the function/method and ids of the parameters
Long answer:
I inspected the content of request.node. Here are the most interesting attributes I found:
class TestClass:
#pytest.mark.parametrize("arg", ["a"])
def test_stuff(self, request, arg):
print("originalname:", request.node.originalname)
print("name:", request.node.name)
print("nodeid:", request.node.nodeid)
Prints the following:
originalname: test_stuff
name: test_stuff[a]
nodeid: relative/path/to/test_things.py::TestClass::test_stuff[a]
NodeID is the most promising if you want to completely identify the test (including the parameters). Note that if the test is as a function (instead of in a class), the class name (::TestClass) is simply missing.
You can parse nodeid as you wish, for example:
components = request.node.nodeid.split("::")
filename = components[0]
test_class = components[1] if len(components) == 3 else None
test_func_with_params = components[-1]
test_func = test_func_with_params.split('[')[0]
test_params = test_func_with_params.split('[')[1][:-1].split('-')
In my example this results to:
filename = 'relative/path/to/test_things.py'
test_class = 'TestClass'
test_func = 'test_stuff'
test_params = ['a']
# content of conftest.py
#pytest.fixture(scope='function', autouse=True)
def test_log(request):
# Here logging is used, you can use whatever you want to use for logs
log.info("STARTED Test '{}'".format(request.node.name))
def fin():
log.info("COMPLETED Test '{}' \n".format(request.node.name))
request.addfinalizer(fin)
Try my little wrapper function which returns the full name of the test, the file and the test name. You can use whichever you like later.
I used it within conftest.py where fixtures do not work as far as I know.
def get_current_test():
full_name = os.environ.get('PYTEST_CURRENT_TEST').split(' ')[0]
test_file = full_name.split("::")[0].split('/')[-1].split('.py')[0]
test_name = full_name.split("::")[1]
return full_name, test_file, test_name
You might have multiple tests, in which case...
test_names = [n for n in dir(self) if n.startswith('test_')]
...will give you all the functions and instance variables that begin with "test_" in self. As long as you don't have any variables named "test_something" this will work.
You can also define a method setup_method(self, method) instead of setup(self) and that will be called before each test method invocation. Using this, you're simply given each method as a parameter. See: http://pytest.org/latest/xunit_setup.html
You could give the inspect module are try.
import inspect
def foo():
print "My name is: ", inspect.stack()[0][3]
foo()
Output: My name is: foo
Try type(self).__name__ perhaps?

How to use unittest.mock to mock arbitrary ConfigParser calls in a unit test

I'm trying to start using unittest.mock's action/assert pattern instead of mox's record/replay/verify pattern.
# foo.py
def op_1(param):
pass
def op_2(param):
pass
def do_stuff(param_1, param_2):
global config
global log
try:
op_1(param_1)
if config.getboolean('section','option'):
op_2(param_2)
except:
log.error("an error occured")
And, here's an example of what my unittest file looks like.
# test_foo.py
class TestFoo(unittest.TestCase):
def test_do_stuff(self):
param_1 = None
param_2 = None
foo.config = MagicMock()
foo.config.getboolean('section','option', return_value = True)
foo.op_1 = MagicMock()
foo.op_2 = MagicMock()
do_stuff(param_1, param_2)
foo.op_1.assert_called_once_with(param_1)
foo.op_2.assert_called_once_with(param_2)
foo.config.getboolean.assert_called_once_with('section','option')
Does this test to verify the items below/am I using mock right?
do_stuff call returned without error
op_1 was called with param_1
op_2 was called with param_2
config parser object had been used, but the specific calls don't matter
It turns out that I was using the return_value wrong.
When I need a mock.Mock or mock.MagicMock object to return a value, it will need to always return that value, regardless of the arguments passed. Though, it might be nice to give different behavior based on arguments passed (possible feature request).
The way I completed this was:
foo.config.getboolean = mock.MagicMock(return_value = True)
And then I can do this:
self.assertGreaterThan(len(foo.config.mock_calls), 0)
self.assertGreaterThan(len(foo.config.getboolean(str(),str())), 0)

Categories