How to access Pytest item outside a test function - python

I want to retrive pytest current running test item and then append my function running data into item.user_properties.
I tried to create dummy plugin and impl pytest_runtest_protocol, update running item in side the hook, then access item from plugin from _pytest.config import get_plugin_manager, plugin_manager.get_plugin('dummy').current_running_item
but it does not work, get_plugin('dummy') return None
How can I achive this or is there a better way?
# my_package/utl.py
from _pytest.config import get_plugin_manager
def my_requester(url):
...
...
manager = get_plugin_manager()
dummy = manager.get_plugin('dummy')
item = dummy.current_item
item.user_properties.append(('requested_url',url))
# conftest.py
import pytest
class Dummy:
def __init__():
self.current_item = None
def pytest_runtest_protocol(self, item, nextitem):
self.current_item = item
def pytest_configure(config):
dummy_plugin = Dummy()
config.pluginmanager.register(dummy_plugin,'dummy')
# test_demo.py
import pytest
from my_package.utl import my_requester
def test_a():
...
my_requester('xxxx')
def test_b():
...
my_requester('yyy')

Found a solution
just use Singleton pattern, set test item to a singleton object property, and append function running data to that property

Related

How to delete test files when python unittest fails

I'm using python unittest for functions that write data to JSON. I use tearDownClass to delete the output test files so they don't clutter the local repo. Ground truths are also stored as JSON files.
I do want to store the output test files when tests fail, so its easier for troubleshooting.
My current implementation is to use a global boolean keep_file = False. When the unittest fails the assertion, it modifies keep_file = True. tearDownClass only deletes the files when keep_file == False. I don't like the idea of modifying global variables and the try exception blocks for each assert.
import json
import os
import unittest
from src.mymodule import foo1, foo2
# These are defined outside the class on purpose so the classmethods can access them
FILE_1 = "unittest.file1.json"
EXPECTED_FILE_1 = "expected.file1.json"
FILE_2 = "unittest.file2.json"
EXPECTED_FILE_2 = "expected.file2.json"
keep_files = False
class TestRhaPostPayload(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.get_file1()
cls.get_file2()
#classmethod
def get_file1(cls):
output1 = foo1()
with open(FILE_1, "w") as f:
f.write(output1)
#classmethod
def get_file2(cls):
output2 = foo1()
with open(FILE_2, "w") as f:
f.write(output2)
#classmethod
def tearDownClass(cls):
if not keep_files:
os.remove(FILE_1)
os.remove(FILE_2)
def test_foo1(self):
# code that reads in file1 and expected_file_1
try:
self.assert(expected_output1, output1)
except AssertionError:
global keep_files
keep_files = True
raise
def test_foo2(self):
# code that reads in file2 and expected_file_2
try:
self.assert(expected_output2, output2)
except AssertionError:
global keep_files
keep_files = True
raise
You could simply check, if there were any errors/failures in your test case during tear-down and only delete the files, if there were none.
How to perform this check was explained in this post.
This check is done on a TestCase instance so tearDownClass won't work. But you are using different files in different tests anyway, so you might as well use normal setUp/tearDown to remove the current file.
Here is a working example:
from pathlib import Path
from typing import Optional
from unittest import TestCase
class Test(TestCase):
def all_tests_passed(self) -> bool:
"""Returns `True` if no errors/failures occurred at the time of calling."""
outcome = getattr(self, "_outcome")
if hasattr(outcome, "errors"): # Python <=3.10
result = self.defaultTestResult()
getattr(self, "_feedErrorsToResult")(result, outcome.errors)
else: # Python >=3.11
result = outcome.result
return all(test != self for test, _ in result.errors + result.failures)
def setUp(self) -> None:
super().setUp()
self.test_file: Optional[Path] = None
def tearDown(self) -> None:
super().tearDown()
if self.test_file and self.all_tests_passed():
self.test_file.unlink()
def test_foo(self) -> None:
self.test_file = Path("foo.txt")
self.test_file.touch()
self.assertTrue(True)
def test_bar(self) -> None:
self.test_file = Path("bar.txt")
self.test_file.touch()
self.assertTrue(False)
Running this test case leaves bar.txt in the current working directory, whereas foo.txt is gone.

replace python class with mocked class

I am new to pytest.
I am trying to mock/replace my client.py with fake_client.py for the testing.
The fake_client class contains the same methods as the client class.
Here is my project structure and code:-
abc/base_class.py
from .client import Client
class PushBase:
def __init__(self, hostname):
self.client = Client(hostname)
def process_product(self, item): # item type is dict {}
product_id = item.get('product_id')
if item.get('state') == 'present':
if not product_id:
# creating product
product_id = self.client.create_product(item.get('data'))
return product_id
# update product
self.client.update_product(item.get('data'))
elif item.get('state') == 'absent':
# delete product
self.client.delete_product(product_id)
This is my client.py with API calls
in abc/client.py
class Client:
def __init__(self, hostname):
self.hostname = hostname
# some other stuff
def create_product(self, params=None):
# some code
def update_product(self, params=None):
# some code
def delete_product(self, params=None):
# some code
I have created a fake client to test against the actual client.py
and it has the same methods as the client.py
in tests/fake_client.py
class FakeClient:
def __init__(self, *args, **kwargs):
pass
def create_product(self):
# some code
def update_product(self):
# some code
def delete_product(self):
# some code
in tests/test_base_class.py
from tests.fake_client import FakeClient
import unittest
from abc.base_class import BaseClass
import pytest
try:
import mock
except ImportError:
from unittest import mock
class TestBaseClassOperations(unittest.TestCase):
def setUp(self):
self.push_base = BaseClass("http://fake_host_nmae/test", "foo", "bar")
self.push_base.client = mock.patch('abc.base_class.Client', new=FakeClient()).start()
def test_create_valid_product(self):
product_dict = { # some stuff }
created_product_id = self.push_base.process_product(product_dict)
# process_product() will call create_product from fake client
# will return 1 if FakeClient().create_product() called
assert created_product_id == 1
I tried it another way.
#pytest.fixture
def fixture_product_creation():
return { # product fixture
}
#mock.patch('abc.base_class.Client', return_value=FakeClient())
class TestBaseClassAgain:
def test_create_valid_product(self, mock_client, fixture_product_creation):
push_base = BaseClass("http://fake_host_nmae/test", "foo", "bar")
created_product_id = push_base.process_product(fixture_product_creation)
expected = 1
assert created_product_id == expected
# how can I use this mock_client here?
Although I can replace the client with the FakeClient, but I am unsure how to arrange all the mock things to get it tested with the assert or assert_called_with calls.
I referred this but not able to arrange it in a proper pythonic way.
Can anyone help me rearrange this and suggest to me any better way to replace the client class with the fake client class by using pytest mock?
Thanks.
In order to properly test this you should make a change to your PushBase class. It is not good practice to instantiate a dependent object in the __init__ method of your class, instead consider passing the object in. This makes testing easier as you can just inject the dependency as needed. Another option would be to make a #classmethod that instantiates the object with the client. In the code below I illustrate how to do the former.
It also appears you have an indentation error as the update_product method can never be called based on the logic you currently have.
# base.py
class PushBase:
def __init__(self, client):
self.client = client
def process_product(self, item): # item type is dict {}
product_id = item.get('product_id')
if item.get('state') == 'present':
if not product_id:
# creating product
product_id = self.client.create_product(item.get('data'))
return product_id
# update product
self.client.update_product(item.get('data'))
elif item.get('state') == 'absent':
# delete product
self.client.delete_product(product_id)
# test_base.py
import pytest
from src.base import PushBase
def test_create_product_id(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "present",
"data": "fizz"
}
mock_client.create_product.return_value = "ok"
product_id = base.process_product(item)
assert product_id == "ok"
mock_client.create_product.assert_called_once_with("fizz")
def test_update_product(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "present",
"data": "bang",
"product_id": "baz"
}
base.process_product(item)
mock_client.update_product.assert_called_once_with("bang")
def test_delete_product(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "absent",
"product_id": "vroom"
}
base.process_product(item)
mock_client.delete_product.assert_called_once_with("vroom")
============================================== test session starts ===============================================
platform darwin -- Python 3.8.9, pytest-7.0.1, pluggy-1.0.0
rootdir: ***
plugins: asyncio-0.18.3, hypothesis-6.48.1, mock-3.7.0
asyncio: mode=strict
collected 3 items
tests/test_base.py ...
=============================================== 3 passed in 0.01s ================================================
I am using the pytest-mock package, which is where the mocker fixture comes from. The nice thing about being able to inject a dependency into your class is you don't need to configure all the methods beforehand, you can modify what you need within each test function. There are improvements you can make to the tests above, but that exercise is left to you. Hopefully this should help you understand the direction you should go in.

Is it possible to patch method called on created previously mock?

I have fixture which create mocker.Mock instead of gui.Menu object during initialization of gui.Buttons. Reference is stored in Buttons.menu attribute. In my test I check if proper function is called in gui.Buttons.add. Parametrized test worked well until I met condition where method from gui.Menu should be called. Now there is a mock.
import pytest
from project import gui
#pytest.fixture
def buttons(mocker):
mocker.patch('project.gui.tk.Frame.__init__', return_value=None)
mocker.patch('project.gui.tk.Button')
return gui.Buttons(mocker.Mock())
#pytest.mark.parametrize('value,patched',(
(None, 'project.gui.show_no_connection'),
(False, 'project.gui.Buttons.process_data'),
(True, 'pytest.Mock.show_error'),
))
def test_add_calls_function(buttons, value, patched, mocker):
mocker.patch('project.gui.Buttons.exist_check', return_value=value)
mocked = mocker.patch(patched)
buttons.add()
mocked.assert_called_once()
With real object I could write (True, 'project.gui.Menu.show_error') inside #pytest.mark.parametrize instead of (True, 'pytest.Mock.show_error') which doesn't work and produces ModuleNotFoundError: No module named 'pytest.Mock'.
I wonder if it is possible to patch created mock object in my fixture to make it work like other parametrized examples. Is it even possible? Correct me please if way how I understand it is wrong.
Tested code looks like:
import tkinter as tk
import tkinter.messagebox as msg
from project.connection import Database
def show_no_connection():
msg.showerror('Error', 'Could not perform operation. Try again later.')
class Menu(tk.Tk):
def __init__(self):
super().__init__()
self.form = Form()
def show_error(self, message):
self.form.clear()
msg.showerror('Error', message)
class Form(tk.Frame):
def clear(self):
print('Clearing...')
def get(self):
return {'Title': 'Test', 'ISBN': 87327837823}
class Buttons(tk.Frame):
def __init__(self, menu):
super().__init__(menu)
self.menu = menu
def process_data(self, data, operation):
operation(data)
def add(self):
data = self.menu.form.get()
exists = self.exist_check(data.get('ISBN', None))
if exists is None:
show_no_connection()
else:
if exists:
self.menu.show_error(
'Record with set ISBN already exists in database.')
else:
self.process_data(data, Database().add)
#staticmethod
def exist_check(number):
if number:
return Database().search({'ISBN': number})
return False
Displayed error:
=================================== FAILURES ===================================
_________ test_add_calls_function[True-project.gui.Gui.show_error] _________
buttons = <[AttributeError("'Buttons' object has no attribute '_w'") raised in repr()] Buttons object at 0x7f840114aa10>
value = True, patched = 'project.gui.Gui.show_error'
mocker = <pytest_mock.plugin.MockFixture object at 0x7f840114ab90>
#pytest.mark.parametrize('value,patched',(
(None, 'project.gui.show_no_connection'),
(False, 'project.gui.Buttons.process_data'),
(True, 'project.gui.Gui.show_error'),
))
def test_add_calls_function(buttons, value, patched, mocker):
mocker.patch('project.gui.Buttons.exist_check', return_value=value)
mocked = mocker.patch(patched)
buttons.add()
> mocked.assert_called_once()
E AssertionError: Expected 'show_error' to have been called once. Called 0 times.
tests/test_gui_buttons.py:88: AssertionError
I don't see a possibility to handle this in the same test - you probably will need a separate test for the last call. The problem is that the menu is already mocked, and you need that specific menu mock to do the test for the function call (the function will be called from that mock instance).
Here is a possible working implementation:
import pytest
# don't use "from project import gui" here to not make a copy in the test module
# that would be used instead of the mocked one
import project.gui
#pytest.fixture
def menu_mock(mocker):
# gives the possibility to access the menu mock
# we need the return_value to get the instance instead of the class
return mocker.patch('project.gui.Menu').return_value
#pytest.fixture
def buttons(mocker, menu_mock):
mocker.patch('project.gui.tk.Frame.__init__', return_value=None)
mocker.patch('project.gui.tk.Button')
return project.gui.Buttons(menu_mock)
#pytest.mark.parametrize('value, patched',(
(None, 'project.gui.show_no_connection'),
(False, 'project.gui.Buttons.process_data')
))
def test_add_calls_function(buttons, value, patched, mocker):
# unchanged except for the missing parametrize case
mocker.patch('project.gui.Buttons.exist_check', return_value=value)
mocked = mocker.patch(patched)
buttons.add()
mocked.assert_called_once()
def test_add_calls_show_error(buttons, menu_mock, mocker):
mocker.patch('project.gui.Buttons.exist_check', return_value=True)
buttons.add()
# you now have access to the mocked menu instance
menu_mock.show_error.assert_called_once()

python mock global function that is used in class

I can't seem to get my head around mocking in Python. I have a global function:
a.py:
def has_permission(args):
ret_val = ...get-true-or-false...
return ret_val
b.py:
class MySerializer(HyperlinkedModelSerializer):
def get_fields():
fields = super().get_fields()
for f in :
if has_permission(...):
ret_val[f.name] = fields[f]
return ret_val
c.py:
class CountrySerializer(MySerializer):
class Meta:
model = Country
Question: Now i want to test c.py, but i want to mock the has_permission function that is defined in a.py, but is called in the get_fields-method of the class MySerializer that is defined in b.py ... How do i do that?
I've tried things like:
#patch('b.MySerializer.has_permission')
and
#patch('b.MySerializer.get_fields.has_permission')
and
#patch('a.has_permission')
But everything i try either just doesn't work and has_permission is still executed, or python complains about that it can't find the attribute 'has_permission'
with the patching done in:
test.py
class TestSerializerFields(TestCase):
#patch(... the above examples....)
def test_my_country_serializer():
s = CountrySerializer()
self..assertTrue(issubclass(my_serializer_fields.MyCharField, type(s.get_fields()['field1'])))
You need to patch the global in the b module:
#patch('b.has_permission')
because that's where your code looks for it.
Also see the Where to patch section of the mock documentation.
You need to patch the method where it exists at the time your test runs. If you try and patch the method where it is defined after the test code has already imported it, then the patch will have no effect. At the point where the #patch(...) executes, the test code under test has already grabbed the global method into its own module.
Here is an example:
app/util/config.py:
# This is the global method we want to mock
def is_search_enabled():
return True
app/service/searcher.py:
# Here is where that global method will be imported
# when this file is first imported
from app.util.config import is_search_enabled
class Searcher:
def __init__(self, api_service):
self._api_service = api_service
def search(self):
if not is_search_enabled():
return None
return self._api_service.perform_request('/search')
test/service/test_searcher.py:
from unittest.mock import patch, Mock
# The next line will cause the imports of `searcher.py` to execute...
from app.service.searcher import Searcher
# At this point, searcher.py has imported is_search_enabled into its module.
# If you later try and patch the method at its definition
# (app.util.config.is_search_enabled), it will have no effect because
# searcher.py won't look there again.
class MockApiService:
pass
class TestSearcher:
# By the time this executes, `is_search_enabled` has already been
# imported into `app.service.searcher`. So that is where we must
# patch it.
#patch('app.service.searcher.is_search_enabled')
def test_no_search_when_disabled(self, mock_is_search_enabled):
mock_is_search_enabled.return_value = False
mock_api_service = MockApiService()
mock_api_service.perform_request = Mock()
searcher = Searcher(mock_api_service)
results = searcher.search()
assert results is None
mock_api_service.perform_request.assert_not_called()
# (For completeness' sake, make sure the code actually works when search is enabled...)
def test_search(self):
mock_api_service = MockApiService()
mock_api_service.perform_request = mock_perform_request = Mock()
searcher = Searcher(mock_api_service)
expected_results = [1, 2, 3]
mock_perform_request.return_value = expected_results
actual_results = searcher.search()
assert actual_results == expected_results
mock_api_service.perform_request.assert_called_once_with('/search')

How to access the py.test capsys from inside a test?

py.test documentations says that I should add capsys parameter to my test methods but in my case this doesn't seem to be possible.
class testAll(unittest.TestCase):
def setUp(self):
self.cwd = os.path.abspath(os.path.split(inspect.getfile(inspect.currentframe()))[0])
os.chdir(self.cwd)
def execute(self, cmd, result=0):
"""
Helper method used by many other tests, that would prevent replicating too much code.
"""
# cmd = "%s > /dev/null 2>&1" % cmd
ret = os.system(cmd) >> 8
self.assertEqual(ret, result, "`%s` returned %s instead of %s (cws=%s)\n\t%s" % (cmd, ret, result, os.getcwd(), OUTPUT)) ### << how to access the output from here
def test_1(self):
self.execute("do someting", 0)
You could define a helper function in the class that inherits the capsys fixture:
#pytest.fixture(autouse=True)
def capsys(self, capsys):
self.capsys = capsys
Then call this function inside the test:
out,err = self.capsys.readouterr()
assert out == 'foobar'
Kudos to MichaƂ Krassowski for his workaround which helped me work through a similar problem.
https://github.com/pytest-dev/pytest/issues/2504#issuecomment-309475790
Thomas Wright's answer is perfect. I'm just sticking this code block here for my own reference as my search led me here and I'll likely forget this in future! [doing a few things in this so useful reference for me]. If anyone is looking and sees where it can be improved - suggest away!
import os
import pytest
from _pytest.monkeypatch import MonkeyPatch
from unittest import TestCase
# -----------------------------------------------------------------------------
def foo_under_test(inp1):
"""Example of a Method under test"""
do_some_calcs_here = inp1*2
get_a_return = ClassCalled.foo_called(do_some_calcs_here)
return get_a_return
# -----------------------------------------------------------------------------
class ClassUnderTest():
"""Example of a Class contained Method under test"""
def __init__(self):
"""Instantiate the class"""
self.var1 = "TestVar"
def foo_under_test2(self, inp11):
"""The class method under test"""
return self.var1 + self.foo_called2(inp11)
def foo_called2(self, inp12):
"""Nominal sub-foo to foo_under_test2"""
return str(inp12*5)
# -----------------------------------------------------------------------------
class ClassCalled:
"""Example of a class that could be called by foo_under_test"""
def foo_called(inp2):
"""Sub-foo to foo_under_test"""
return inp2 * 2
# -----------------------------------------------------------------------------
class MockResponses:
"""Class for holding the mock responses"""
def foo_called(inp2):
"""**Mock of foo_called**"""
return inp2*3
def foo_called2(inp12):
"""**Mock of foo_called**"""
return str(inp12*4)
# -----------------------------------------------------------------------------
class Test_foo_under_test(TestCase):
"""Test class - means of grouping up tests for a target function
This one is addressing the individual function (not within a class)
"""
# ---------------------------------------------------------------
#pytest.fixture(autouse=True)
def capsys(self, capsys):
"""Capsys hook into this class"""
self.capsys = capsys
def print_to_console(self, strOut):
"""Print strOut to console (even within a pyTest execution)"""
with self.capsys.disabled():
print(strOut)
def setUp(self):
"""Ran by pyTest before running any test_*() functions"""
self.monkeypatch = MonkeyPatch()
# ---------------------------------------------------------------
def test_1(self):
"""**Test case**"""
def mock_foo_called(inp2):
return MockResponses.foo_called(inp2)
mockedFoo = ClassCalled.foo_called # Need to get this handle here
self.monkeypatch.setattr(ClassCalled, "foo_called", mock_foo_called)
x = foo_under_test(1)
self.print_to_console("\n")
strOut = "Rtn from foo: " + str(x)
self.print_to_console(strOut)
assert x == 6
# Manually clear the monkey patch
self.monkeypatch.setattr(
ClassCalled, "foo_called", mockedFoo)
"""I've noticed with me having monkeypatch inside the
class, the damn thing persists across functions.
This is the only workaround I've found so far"""
# -----------------------------------------------------------------------------
class Test_ClassUnderTest_foo_under_test(TestCase):
"""Test class - means of grouping up tests for a target function
This one is addressing the function within a class
"""
# ---------------------------------------------------------------
#pytest.fixture(autouse=True)
def capsys(self, capsys):
"""Capsys hook into this class"""
self.capsys = capsys
def print_to_console(self, strOut):
"""Print strOut to console (even within a pyTest execution)"""
with self.capsys.disabled():
print(strOut)
def setUp(self):
"""Ran by pyTest before running any test_*() functions"""
self.monkeypatch = MonkeyPatch()
# ---------------------------------------------------------------
def test_1(self):
"""**Test case**"""
def mock_foo_called2(self, inp2):
"""
Mock function
Defining a mock function, note this can be dealt with directly
here, or if its more comprehensible, put it in a separate class
(i.e. MockResponses)
"""
# return MockResponses.foo_called2(inp2) # Delegated approach
return str(inp2*4) # Direct approach
"""Note that the existence of self within this test class forces
a wrapper around calling a MockClass - so we have to go through
both the line below and the function mock_foo_called2() above to
properly invoke MockResponses.foo_called2()
"""
mockedFoo = ClassUnderTest.foo_called2
self.monkeypatch.setattr(
ClassUnderTest, "foo_called2", mock_foo_called2)
x = ClassUnderTest().foo_under_test2(1)
strOut = "Rtn from foo: " + str(x)
self.print_to_console("\n")
self.print_to_console(strOut)
assert x == "TestVar" + str(4)
self.monkeypatch.setattr(
ClassUnderTest, "foo_called2", mockedFoo)
# -----------------------------------------------------------------------------
# ---- Main
if __name__ == "__main__":
#
# Setup for pytest
outFileName = os.path.basename(__file__)[:-3] # Remove the .py from end
currScript = os.path.basename(__file__)
# -------------------------------------------------------------------------
# PyTest execution
pytest.main([currScript, "--html", outFileName + "_report.html"])
rtnA = foo_under_test(1)
print(rtnA == 4)
# This should output 4, demonstrating effect of stub (which produced 6)
rtnB = ClassUnderTest().foo_under_test2(1)
print(rtnB == "TestVar"+str(5))
# This should output "TestVar5", demonstrating effect of stub
# conftest.py
class TTY:
def communicate(self):
with self.trace():
print('wow!')
#pytest.fixture(autouse=True)
def set_capsys(capsys):
TTY.trace = capsys.disabled
#pytest.fixture
def get_tty():
_get_tty():
return TTY()
return _get_tty
# test_wow.py
def test_wow(get_tty):
get_tty().communicate()

Categories

Resources