I have a test class and a setup function that looks like this:
#pytest.fixture(autouse=True, scope='function')
def setup(self, request):
self.client = MyClass()
first_patcher = patch('myclass.myclass.function_to_patch')
first_mock = first_patcher.start()
first_mock.return_value = 'foo'
value_to_return = getattr(request, 'value_name', None)
second_patcher = patch('myclass.myclass.function_two')
second_mock = second_patcher.start()
second_mock.return_value = value_to_return
#could clean up my mocks here, but don't care right now
I see in the documentation for pytest, that introspection can be done for a module level value:
val = getattr(request.module, 'val_name', None)
But, I want to be able to specify different values to return based on the test I am in. So I am looking for a way to introspect the test_function not the test_module.
http://pytest.org/latest/fixture.html#fixtures-can-introspect-the-requesting-test-context
You can use request.function to get to the test function. Just follow the link on the b wepage you referenced to see what is available on the test request object :)
Maybe the documentation has changed since the time of the accepted answer.
At least for me it was not clear how to
Just follow the link
So I thought I'd update this thread with the link itself:
https://pytest.org/en/6.2.x/reference.html#request
Edit December 2021
Even when the link is correct now I think this statement from the pytest documentation is just not correct:
Fixture functions can accept the request object to introspect the “requesting” test function ...
While I found some examples for getting attributes of the module I did not find a single working example of introspecting the test function that requests the fixture. May be related to collection and runtime order.
What really helped me to get the desired behavior was to use the factory idiom a little further down in the pytest documentation:
https://pytest.org/en/6.2.x/fixture.html#factories-as-fixtures
Set up the fixture factory
#pytest.fixture(scope='function')
def getQueryResult() -> object:
def _impl(_mrId: int = 7622):
return QueryResult(_mrId)
return _impl
Usage
# Concrete value
def test_foo(getQueryResult):
queryResult = getQueryResult(4711)
...
# Default value
def test_bar(getQueryResult):
queryResult = getQueryResult()
...
Related
I am working on some functional tests for my application. Depending on the logged user's permissions, the sidebar will have different links. I am parameterizing them (hard coded) and running a test that works well (app is a webtest app):
endpoints = [
'/',
'/endpoint1',
'endpoint2',
...
]
#pytest.mark.parametrize('endpoint', endpoints)
def test_endpoints(endpoint, app):
res = app.get(endpoint).maybe_follow()
assert res.status_code == 200
I would like to avoid having to hard code the list of links for each type of user. Inside a fixture I can actually get them programmatically, so ideally I would like to parametrize the return value of this fixture in order to run the test function above:
#pytest.fixture
def endpoints(app):
res = app.get('/login').follow()
sidebar_links = []
for link in res.html.ul.find_all('a'):
if link.has_attr('href') and not link['href'].startswith('#'):
sidebar_links.append(link['href'])
return sidebar_links
Is this possible?
I would suggest that you use the pytest_configure() hook instead as this method will run before all your test methods. in conftest.py file you can keep a global variable as pytest.endpoints= [] then later in the hook method keep on appending the value of endpoints to this variable
something like this
pytest.endpoints= []
def pytest_configure(config,app):
res = app.get('/login').follow()
for link in res.html.ul.find_all('a'):
if link.has_attr('href') and not link['href'].startswith('#'):
pytest.endpoints.append(link['href'])
within the test method use the same variable as a parameter like below
#pytest.mark.parametrize("endpoint",pytest.endpoints)
def test_endpoints(endpoint):
Well i am not completely aware of your design so i cannot suggest any further but you can give this a try.
I asked the same question in GitHub.
I learned about pytest-helpers-namespace from s0undt3ch in his very helpful answer. However I found a usecase I cant seem to find an obvious workaround. Here is the paste of my original question on GitHub.
How can I use the fixtures already declared in my conftest within my helper functions?
I am have a large, memory heavy configuration object (for simplicity, a dictionary) in all test, but I dont want to tear it down and rebuild this object, thus scoped as session and reused. Often times, I want to grab values from the configuration object within my test.
I know reusing fixtures within fixtures, you have to pass a reference
# fixtures
#pytest.fixture(scope="session")
def return_dictionary():
return {
"test_key": "test_value"
}
#pytest.fixture(scope="session")
def add_random(return_dictionary):
_temp = return_dictionary
_temp["test_key_random"] = "test_random_value"
return _temp
Is it because pytest collects similar decorators, and analyzes them together? I would like someone's input into this. Thanks!
Here is a few files I created to demonstrate what I was looking for, and what the error I am seeing.
# conftest.py
import pytest
from pprint import pprint
pytest_plugins = ["helpers_namespace"]
# fixtures
#pytest.fixture(scope="session")
def return_dictionary():
return {
"test_key": "test_value"
}
# helpers
#pytest.helpers.register
def super_print(_dict):
pprint(_dict)
#pytest.helpers.register
def super_print_always(key, _dict=return_dictionary):
pprint(_dict[key])
# test_check.py
import pytest
def test_option_1(return_dictionary):
print(return_dictionary)
def test_option_2(return_dictionary):
return_dictionary["test_key_2"] = "test_value_2"
pytest.helpers.super_print(return_dictionary)
def test_option_3():
pytest.helpers.super_print_always('test_key')
key = 'test_key', _dict = <function return_dictionary at 0x039B6C48>
#pytest.helpers.register
def super_print_always(key, _dict=return_dictionary):
> pprint(_dict[key])
E TypeError: 'function' object is not subscriptable
conftest.py:30: TypeError
I have an object that is used for fetching information from another service which is very simple. Since the object is simple and the initialization method could be easily patched I thought I would try to write my code to be super reusable and extendable. But alas, I cannot figure out how to make it work. The code below is pretty well sudo code and is super simplified but it should get the point across.
class SimpleClient:
def __init__(self):
pass
def read(self, key, path='some/path'):
return value_from_get_on_another_service
I then have a request handler object that initializes a client via get_client() (seen below)
def get_client():
return SimpleClient()
Then a method on the request handler uses the client.read() method a few times with different parameters (2nd dependent upon the 1st).
For my tests, I thought I could "patch" the get_client method to return my own simple object that could then be used "regularly" and eliminate the dependence on the third party service and actually use the values retrieved from the method execution. I was disappointed to find it was not that easy and clean. The test pattern is seen below.
class MockClient:
def __init__(self, addr='someAddr', token='someToken'):
pass
def read(self, value, prefix):
data = {}
if prefix == 'path/1':
data = self.p1_lookup(value)
elif prefix == 'path/2':
data = self.p2_lookup(value)
return self.response_wrapper(data)
def p2_lookup(self, key):
data = {
'key1': {
'sub_key': {"55B3FE7D-9F43-4DD4-9090-9D89330C918A": "Dev2",
"7A1C2F4B-E91C-4659-A33E-1B18B0BEE2B3": "Dev"}
}
}
return data.get(key, {})
#mock.patch('a.module.get_client')
def test_authorize_valid_request_no_body(mock_get_client):
request = RequestMock()
request.body = None
handler = RequestHandler(Application(), request=request, logging_level='INFO')
mock_get_client.return_value = MockClient()
handler.authorize_request()
assert handler.verified_headers is None
assert handler.verified_body is None
assert handler.user_authenticated is False
I have seen where I can mock the responses for the actual client.read() to return multiple values with a list. But this just seems like I will be doing lots of copy and paste and have to do the same thing over and over for each little test. Forgive me if this is simple, sadly I am just learning the art of testing. Is there a way to accomplish what I am trying to do? Maybe there is something super simple I am missing. Or maybe I am just totally on the wrong track for no good reason. Help?!
After a sleep, with fresh eyes I was able to figure this out relatively quickly thanks to a couple other similar questions/answers that I had not found before. Primarily this one, Python Mock Object with Method called Multiple Times.
Rather than needing to rebuild the module object completely I need to let mock do that for me and then override the specific method on it with the side_effect attribute. So below is what sanitized version of the code looks like.
def read_override(value, prefix):
lookup_data1 = {"lookup1": {'key1': 'value1'}}
lookup_data2 = {'some_id': {'akey': {'12345678': 'DEV'}}
data = {}
if prefix == 'path1/1a':
data = lookup_data1.get(value, {})
elif prefix == 'path2/2a':
data = lookup_data2.get(value, {})
return {'data': data}
# Create a true Mock of the entire LookupClient Object
VAULT_MOCK = mock.Mock(spec=LookupClient)
# make the read method work the way I want it to with an "override" of sorts
VAULT_MOCK.read.side_effect = vault_read_override
Then the test simply looked like this...
#mock.patch('a.module.get_client')
def test_authorize_valid_request_no_body(get_client):
get_client.return_value = VAULT_MOCK
request = RequestMock()
request.body = None
handler = RequestHandler(Application(), request=request, logging_level='INFO')
handler.authorize_request()
assert handler.verified_headers is None
assert handler.verified_body is None
assert handler.user_authenticated is False
I am in the process of learning unit testing, however I am struggling to understand how to mock functions for unit testing. I have reviewed many how-to's and examples but the concept is not transferring enough for me to use it on my code. I am hoping getting this to work on a actual code example I have will help.
In this case I am trying to mock isTokenValid.
Here is example code of what I want to mock.
<in library file>
import xmlrpc.client as xmlrpclib
class Library(object):
def function:
#...
AuthURL = 'https://example.com/xmlrpc/Auth'
auth_server = xmlrpclib.ServerProxy(AuthURL)
socket.setdefaulttimeout(20)
try:
if pull == 0:
valid = auth_server.isTokenValid(token)
#...
in my unit test file I have
import library
class Tester(unittest.TestCase):
#patch('library.xmlrpclib.ServerProxy')
def test_xmlrpclib(self, fake_xmlrpclib):
assert 'something'
How would I mock the code listed in 'function'? Token can be any number as a string and valid would be a int(1)
First of all, you can and should mock xmlrpc.client.ServerProxy; your library imports xmlrpc.client as a new name, but it is still the same module object so both xmlrpclib.ServerProxy in your library and xmlrpc.client.ServerProxy lead to the same object.
Next, look at how the object is used, and look for calls, the (..) syntax. Your library uses the server proxy like this:
# a call to create an instance
auth_server = xmlrpclib.ServerProxy(AuthURL)
# on the instance, a call to another method
valid = auth_server.isTokenValid(token)
So there is a chain here, where the mock is called, and the return value is then used to find another attribute that is also called. When mocking, you need to look for that same chain; use the Mock.return_value attribute for this. By default a new mock instance is returned when you call a mock, but you can also set test values.
So to test your code, you'd want to influence what auth_server.isTokenValid(token) returns, and test if your code works correctly. You may also want to assert that the correct URL is passed to the ServerProxy instance.
Create separate tests for different outcomes. Perhaps the token is valid in one case, not valid in another, and you'd want to test both cases:
class Tester(unittest.TestCase):
#patch('xmlrpc.client.ServerProxy')
def test_valid_token(self, mock_serverproxy):
# the ServerProxy(AuthURL) return value
mock_auth_server = mock_serverproxy.return_value
# configure a response for a valid token
mock_auth_server.isTokenValid.return_value = 1
# now run your library code
return_value = library.Library().function()
# and make test assertions
# about the server proxy
mock_serverproxy.assert_called_with('some_url')
# and about the auth_server.isTokenValid call
mock_auth_server.isTokenValid.assert_called_once()
# and if the result of the function is expected
self.assertEqual(return_value, 'expected return value')
#patch('xmlrpc.client.ServerProxy')
def test_invalid_token(self, mock_serverproxy):
# the ServerProxy(AuthURL) return value
mock_auth_server = mock_serverproxy.return_value
# configure a response; now testing for an invalid token instead
mock_auth_server.isTokenValid.return_value = 0
# now run your library code
return_value = library.Library().function()
# and make test assertions
# about the server proxy
mock_serverproxy.assert_called_with('some_url')
# and about the auth_server.isTokenValid call
mock_auth_server.isTokenValid.assert_called_once()
# and if the result of the function is expected
self.assertEqual(return_value, 'expected return value')
There are many mock attributes to use, and you can change your patch decorator usage a little as follows:
class Tester(unittest.TestCase):
def test_xmlrpclib(self):
with patch('library.xmlrpclib.ServerProxy.isTokenValid') as isTokenValid:
self.assertEqual(isTokenValid.call_count, 0)
# your test code calling xmlrpclib
self.assertEqual(isTokenValid.call_count, 1)
token = isTokenValid.call_args[0] # assume this token is valid
self.assertEqual(isTokenValid.return_value, 1)
You can adjust the code above to satisfy your requirements.
I have several pytest test cases that need nearly identical setup, so I would like to have them reuse a fixture to keep things DRY. The setup involves creating a new ticket in an external ticket tracking system, then the test cases interact with the ticket based on the data, and finally the fixture cleans up by closing out the ticket. The challenge here is that each test case needs slightly different data to be prepped in the ticket.
Each test case has different calls and different assertions, so I can't combine them all into a single parametrized test case with a single test fixture. Parametrizing the the fixture itself would result in every test case running every permutation of the fixture data, which ends up with a lot of irrelevant test failures.
What I would like to do is set a variable in the test case, then have the fixture use that variable to set up the test data when creating the ticket. I've tried to use request.function as specified in the pytest fixture docs but I keep getting:
=================================== ERRORS ===================================
____________________ ERROR at setup of TestMCVE.test_stuff ___________________
request = <SubRequest 'ticket' for <Function 'test_stuff'>>
#pytest.yield_fixture
def ticket(request):
> ticket_summary = getattr(request.function, "summary")
E AttributeError: 'function' object has no attribute 'summary'
tests\test_mcve.py:11: AttributeError
My code is:
import pytest
def ticket_system_api(summary):
# stub for MCVE purposes
return summary
#pytest.yield_fixture
def ticket(request):
ticket_summary = getattr(request.function, "summary")
new_ticket = ticket_system_api(summary=ticket_summary)
yield new_ticket
class TestMCVE:
def test_stuff(self, ticket):
summary = 'xyz'
# do real things here, except MCVE
assert 'xyz' == ticket
I've tried using request.node instead of request.function as well as binding the summary variable per this answer, changing summary = 'xyz' to test_stuff.summary = 'xyz' but these still fail with the same AttributeError.
How can I pass the function level data to the fixture?
You can accomplish this with indirect parametrization. The API (and the documentation) could be friendlier, but the functionality you want is there.
Your example was very close, and minor tweaks were needed. Take a look:
import pytest
def ticket_system_api(summary):
# stub for MCVE purposes
return summary
#pytest.fixture
def ticket(request):
# NOTE: This will raise `AttributeError` if the fixture
# doesn't receive a parameter.
ticket_summary = request.param
new_ticket = ticket_system_api(summary=ticket_summary)
return new_ticket
class TestMCVE:
#pytest.mark.parametrize('ticket', ('abc',), indirect=True)
def test_abc(self, ticket):
# do real things here, except MCVE
assert ticket == 'abc'
#pytest.mark.parametrize('ticket', ('xyz',), indirect=True)
def test_xyz(self, ticket):
# do real things here, except MCVE
assert ticket == 'xyz'