I have one function which does the some process and if fails then i am existing the code using sys.exit(myfun()) but when i am testing using pytest i dont want to execute function myfun() inside sys.exit(). is there anyway in pytest it can be possible skip myfun()?
mypyfile.py
def process():
# do some logic
if failed:
sys.exit(myfun()) # this i dont want to execute if i test via my pytest
def myfun():
print("failed")
test_mypyfile.py
import pytest
import test_mypyfile
def test_process():
test_mypyfile.process()
You could mock myfunc in your test. This way the function is not called in the test process. I'll change a bit you're example so that it make more sense to mock (and you'll be able to see the difference):
# mypyfile.py
from time import sleep
def process():
# do some logic
if failed:
sys.exit(myfunc)
def myfun():
sleep(4000)
# test_mypyfile.py
import mock
import mypyfile
def test_process():
mock.patch('mypyfile.myfunc')
test_mypyfile.process()
Here myfunc will be called but you won't wait the 4000s.
Related
Let's say i have a method is_validate, which internally calls validate method from library gateway.service
import gateway.service
from gateway.service.exception import ValidatorException
def is_validate():
try:
gateway.service.validate() # which throws ValidatorException
return True
except ValidatorException ex:
return False
How to unit test is_validate method, mocking gateway.service.validate to throw ValidatorException ?
You can do this with a combination of:
mocking a function (creating a fake version of the function dictating what it returns);
monkeypatching the actual function with your mock version;
and using pytest to actually run the test.
I've written a description of how to do this (pulled from my own work) here, in case an example I know works is useful.
But this is what I think you'll need to do in your code:
Define a pytest fixture to mock the scenario you want to test, using monkeypatch to fake the results you want from the parts of the is_validate().
And a test to check that a ValidatorException is raised; the code that raises the error in the test is in the pytest fixture. The entire pytest fixture defined there is passed as a parameter to the test.
import pytest
from unittest import TestCase
import gateway.service
from gateway.service.exception import ValidatorException
# Create object for accessing unittest assertions
assertions = TestCase("__init__")
#pytest.fixture
def validation_fails(monkeypatch):
"""
Mocks a call to gateway.service.validate().
"""
def mock_validate(*args, **kwargs):
"""Mock an absolute file path."""
raise ValidatorException
# Replace calls to existing methods with the mocked versions
monkeypatch.setattr(gateway.service, "validate", mock_validate)
def test_validation_fails(validation_fails):
"""Test validation."""
# check that the correct exception is raised
with assertions.assertRaises(ValidatorException):
is_validate()
Note: This does not include whatever setup is required to get pytest working for your project.
-------------------------------------
mymodule.py
-------------------------------------
import os
def remove(file_path):
if os.path.exists(file_path):
os.remove(file_path)
else:
print('File does not exist')
-------------------------------------
from mymodule import remove
import mock
import unittest
class RemoveTestCase(unittest.TestCase):
#mock.patch('mymodule.os.path')
#mock.patch('mymodule.os.remove')
def test_remove(self, mock_os_remove, mock_os_path):
mock_os_path.exists.return_value = True
#side_effect
remove("any path")
mock_os_remove.assert_called_with("any path")
I was able to mock gateway.service.validate by referencing it with module name where is_validate method is present.
ex: #mock.patch('mymodule.gateway.service.validate')
Reference this doc for more info
pytest : How to write pytest code to detect a "func" called without actually executing the "func" ,
target.py:
import requests
import threading
CFG=None
targets={}
def add_target(func):
targets[func.__name__] = func
return func
def parent_func(url):
for funct in set(targets):
threading.Thread(target=targets[funct], args=(CFG[url],)).start()
#add_target
def child_func(url):
try:
response = requests.request("POST", url,
headers={"Content-Type": "application/json"},
data="{\"text\": \"target py test\"}")
response.raise_for_status()
except Exception as err:
raise RuntimeError(err)
test_target.py:
During testing, I want child_func() to get called but the child_func() body should not get executed. Rather, after running tests, child_func() gets executed & test run results in "AssertionError: False is not true "
from unittest.mock import Mock, patch
import target
# Third-party imports...
from nose.tools import assert_true, assert_is_not_none
target.CFG={'url':"some valid url"}
#patch('target.child_func')
def test_child_func(mock_child_func):
parent_func("url")
assert_true(mock_child_func.called)
First (for the sake of my own sanity) let's whittle this down to an actual MRE -- we don't actually need threading or requests or any of the implementation details of child_func to demonstrate the problem here.
target.py:
targets={}
def add_target(func):
targets[func.__name__] = func
return func
def parent_func(url):
for func in targets.values():
func(url)
#add_target
def child_func(url):
raise NotImplementedError("The test shouldn't actually call this function!")
test_target.py:
from unittest.mock import Mock, patch
from target import parent_func
#patch('target.child_func')
def test_parent_func(mock_child_func):
parent_func("url")
assert mock_child_func.call_count == 1
Within our test, we want parent_func to call mock_child_func, not the real child_func. But when we run our test, we can quickly see that our patch didn't work:
============================================== short test summary info ===============================================
FAILED test_target.py::test_parent_func - NotImplementedError: The test shouldn't actually call this function!
The reason for this is that parent_func doesn't call child_func, it calls targets['child_func']. Patching 'target.child_func' doesn't modify the actual function, it modifies what the name child_func points to in the target module -- and that name isn't in use in the code under test!
The easiest fix here is to patch target.targets:
from unittest.mock import Mock, patch
from target import parent_func
#patch('target.targets', new_callable=dict)
def test_parent_func(mock_targets):
mock_targets['child_func'] = Mock()
parent_func("url")
assert mock_targets['child_func'].call_count == 1
Now our test passes because when parent_func iterates through its targets, it gets our mock_targets dict, which we can populate with whatever functions/mocks we want.
In Python 3.10, I have a function like:
from shutil import which
def my_func():
if which('myexecutable.sh'):
# do stuff
else:
# do other stuff
I would like to write a unit test with Pytest that runs the first part code even though the executable is not present. What is the best way to do this?
I know that I can use monkeypatch.setenv() to set an environment variable, but that's not going to make the which() check pass. There's also the added challenge of making sure this is compatible on Windows and Linux.
You could try like this:
# in script file
from shutil import which
def myfunc():
if which("myexecutable.sh"):
return "OK"
else:
...
# in test file
import pytest
from script import myfunc
#pytest.fixture
def which(mocker):
return mocker.patch("script.which", autospec=True)
def test_myfunc(which):
assert myfunc() == "OK"
Running pytest outputs: 1 passed
Does pytest provides functionality like unittest.mock to check if the mock was actually called once(or once with some parameter)?
Sample Source code:
my_package/my_module.py
from com.abc.validation import Validation
class MyModule:
def __init__(self):
pass
def will_call_other_package(self):
val = Validation()
val.do()
def run(self):
self.will_call_other_package()
Sample test code for the above source code:
test_my_module.py
import pytest
from pytest_mock import mocker
from my_package.my_module import MyModule
#pytest.fixture
def mock_will_call_other_package(mocker):
mocker.patch('my_package.my_module.will_call_other_package')
#pytest.mark.usefixtures("mock_will_call_other_package")
class TestMyModule:
def test_run(self):
MyModule().run()
#check `will_call_other_package` method is called.
#Looking for something similar to what unittest.mock provide
#mock_will_call_other_package.called_once
If you want to use a fixture that does the patching, you can move the patching into a fixture:
import pytest
from unittest import mock
from my_package.my_module import MyModule
#pytest.fixture
def mock_will_call_other_package():
with mock.patch('my_package.my_module.will_call_other_package') as mocked:
yield mocked
# the mocking will be reverted here, e.g. after the test
class TestMyModule:
def test_run(self, mock_will_call_other_package):
MyModule().run()
mock_will_call_other_package.assert_called_once()
Note that you have to use the fixture parameter in the test. Just using #pytest.mark.usefixtures will not give you access to the mock itself. You can still use it to be effective in all tests in the class, if you don't need to access the mock in all tests (or use autouse=True in the fixture).
Also note that you don't need pytest-mock here - but as mentioned by #hoefling, using it makes the fixture better readable, because you don't need the with clause :
#pytest.fixture
def mock_will_call_other_package(mocker):
yield mocker.patch('my_package.my_module.will_call_other_package')
As an aside: you don't need to import mocker. Fixtures are looked up by name, and available automatically if the respective plugin is installed.
You could try this:
import pytest
from my_package.my_module import MyModule
def test_run(mocker):
mocker.patch('my_package.my_module.will_call_other_package')
MyModule().run()
mock_will_call_other_package.assert_called_once()
First of all, you may not need the burden of an external library such as pytest_mock, because pytest already got you covered using the integration with unittest.
You also do not need to use the usefixtures because whenever you need a fixture, you just receive it in your test method.
An ideal scenario based on your own code would look similar to this:
import pytest
from unittest.mock import patch
from com.abc.validation import Validation
class MyModule:
def __init__(self):
pass
def will_call_other_package(self):
val = Validation()
val.do()
def run(self):
self.will_call_other_package()
#pytest.fixture
def call_other_module():
with patch("my_package.my_module.MyModule.will_call_other_package") as _patched:
yield _patched
class TestMyModule:
def test_run_will_call_other_package(self, call_other_module):
call_other_module.assert_not_called()
obj = MyModule()
call_other_module.assert_not_called()
obj.run()
call_other_module.assert_called_once()
And also if you want to make sure that you did infact patch the target MyModule.will_call_other_package, modify your test like this:
class TestMyModule:
def test_run_will_call_other_package(self, call_other_module):
call_other_module.assert_not_called()
obj = MyModule()
call_other_module.assert_not_called()
obj.run()
call_other_module.assert_called_once()
assert False, (MyModule.will_call_other_package, call_other_module)
And you'll see something similar to this:
AssertionError: (<MagicMock name='will_call_other_package' id='140695551841328'>, <MagicMock name='will_call_other_package' id='140695551841328'>)
As you can see the id of both objects are the same, confirming our experiment was successful.
I have a test class test_enrollment.py that defines a set of tests, and has a main method which uses a generator to generate a new custom test class. In a simpler form, it looks like:
import json
import unittest
import front_end_tests.generator
class EnrollmentTests(unittest.TestCase):
def test_new_student_registration(self):
# test definition here
if __name__ == '__main__':
front_end_tests.generator.generate_and_run_tests(EnrollmentTests)
The way I normally run this test class is by calling python test_enrollment.py. If I wanted to run this same test file with pytest instead, is there a way to call the file in a way where the main method still gets called?
I thought you can`t.
To solve this problem, you just need to move
front_end_tests.generator.generate_and_run_tests(EnrollmentTests)
To
def setUpModule():
front_end_tests.generator.generate_and_run_tests(EnrollmentTests)
test_enrollment.py look like this:
import json
import unittest
import front_end_tests.generator
def setUpModule():
front_end_tests.generator.generate_and_run_tests(EnrollmentTests)
class EnrollmentTests(unittest.TestCase):
def test_new_student_registration(self):
# test definition here
pass
if __name__ == '__main__':
unittest.main()
and just type:
pytest