Having following file handler.py
import job
def worker():
return job()
And following test test_handler.py
import subprocess
def test_worker():
subprocess.Popen(['./handler.py'], stderr=subprocess.PIPE)
How can I mock job function, taking into account that handler.py is called in subprocess
This isn't the best approach to test this feature.
Since it is a unit test, you should test the worker and check it's value.
handler.py
import job
def worker():
return job()
test_handler.py
import unittest
from handler import worker
class TestHandlerCase(unittest.TestCase):
def test_worker(self):
"""Test call worker should succeed."""
self.assertEqual(worker(), True)
if __name__ == '__main__':
unittest.main()
Check if the result of method is equal to the expected. And so, adjust the method/test.
Anyway, this is an example, based on you question code.
Related
I am trying to figure out how to use the #patch.object to mock a __init__ and, log.write() for the Logger class, which is imported but isn't used inside the function of a module. The tuorial such as this, https://www.pythontutorial.net/python-unit-testing/python-patch/
, points to the patching needs to be at the target where it is used, not where it comes from. However, every example used shows the target to be mocked inside another function.
In the use case mentioned below, the logger is imported and used to write the log outside the scope of a function. Is there a way to mock the behavior both in main.py and routers.py?
src/apis/main.py
from utils.log import Logger
from routes import route
log = Logger(name="logger-1")
log.write("logger started")
def main():
log = Logger(name="logger-1")
log.write("inside main")
route()
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO) # for demo
main()
In src/apis/routers/routes.py
from utils.log import Logger
log = Logger(name="logger-1")
log.write, message=f"Inside route")
def route():
log.write, message=f"Logging done.")
In utils/log/logging.py
import logging
Class Logger:
def __init__(self, name):
# needs to be mocked
def write(self, message):
# needs to be mocked to return None
When asking a question, it is very convenient to offer a Minimal Reproducible Example. So remove the unnecessary fastapi and starlette, and provide the code for the test you are trying to write.
Here it is :
# file: so74695297_main.py
from so74695297_log import Logger
from so74695297_routes import my_route
log = Logger(name="logger-1")
log.write("logger started")
def main(): # FAKE IMPL
log.write(message=f"in main()")
my_route()
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO) # for demo
main()
# file: so74695297_routes.py
from so74695297_log import Logger
log = Logger(name="logger-1")
def my_route(): # FAKE IMPL
log.write(message=f"route")
# file: so74695297_log.py
import logging
class Logger:
def __init__(self, name):
self._logger = logging.getLogger(name) # FAKE IMPL
def write(self, message):
self._logger.info(message) # FAKE IMPL
when ran (the main.py file does something) :
INFO:logger-1:in main()
INFO:logger-1:route
Which is the expected output when the loggers don't fave any formatter.
Then adding a test :
# file: so74695297_test.py
import unittest
import unittest.mock as mock
from so74695297_routes import my_route
class TestMyRoute(unittest.TestCase):
def test__my_route_write_a_log(self):
spy_logger = mock.Mock()
with mock.patch("so74695297_log.Logger", new=spy_logger):
my_route()
assert spy_logger.assert_called()
if __name__ == "__main__":
unittest.main() # for demo
Ran 1 test in 0.010s
FAILED (failures=1)
Failure
Traceback (most recent call last):
File "/home/stack_overflow/so74695297_test.py", line 12, in test__my_route_write_a_log
assert spy_logger.assert_called()
File "/usr/lib/python3.8/unittest/mock.py", line 882, in assert_called
raise AssertionError(msg)
AssertionError: Expected 'mock' to have been called.
Now we have something to work with !
As #MrBeanBremen indicated, the fact that your logger is configured at import time (even when not being the "main" module) complicates things.
The problem is that, by the time the mock.patch line runs, the modules have already been imported and created their Logger. What we could do instead is mock the Logger.write method :
def test__my_route_writes_a_log(self):
with mock.patch("so74695297_log.Logger.write") as spy__Logger_write:
my_route()
spy__Logger_write.assert_called_once_with(message="route")
Ran 1 test in 0.001s
OK
If you prefer using the decorator form :
#mock.patch("so74695297_log.Logger.write")
def test__my_route_writes_a_log(self, spy__Logger_write):
my_route()
spy__Logger_write.assert_called_once_with(message="route")
Because we mocked the class's method, each Logger instance has a mock version of write :
# vvvv
#mock.patch("so74695297_main.Logger.write")
def test__main_writes_a_log(self, spy__Logger_write):
main()
# assert False, spy__Logger_write.mock_calls
spy__Logger_write.assert_any_call(message="in main()")
In the end, main.Logger.write is essentially the same thing as routes.Logger.write and as log.Logger.write, just a reference to the same "method" object. Mock from one way, mock for all the others too.
I have code which uses Python requests to kick off a task which runs in a worker that is started with rq. (Actually, the GET request results in one task which itself starts a second task. But this complexity shouldn't affect things, so I've left that out of the code below.) I already have a test which uses rq's SimpleWorker class to cause the code to run synchronously. This works fine. But now I'm adding requests_ratelimiter to the second task, and I want to be sure it's behaving correctly. I think I need to somehow mock the time.sleep() function used by the rate limiter, and I can't figure out how to patch it.
routes.py
#app.route("/do_work/", methods=["POST"])
def do_work():
rq_job = my_queue.enqueue(f"my_app.worker.do_work", job_timeout=3600, *args, **kwargs)
worker.py
from requests_ratelimiter import LimiterSession
#job('my_queue', connection=redis_conn, timeout=3600, result_ttl=24 * 60 * 60)
def do_work():
session = LimiterSession(per_second=1)
r = session.get(WORK_URL)
test.py
import requests_mock
def test_get(client):
# call the Flask function to kick off the task
client.get("/do_work/")
with requests_mock.Mocker() as m:
# mock the return value of the requests.get() call in the worker
response_success = {"result": "All good"}
m.get(WORK_URL, json=response_success)
worker = SimpleWorker([my_queue], connection=redis_conn)
worker.work(burst=True) # Work until the queue is empty
A test in requests_ratelimiter patches the sleep function using a target path of 'pyrate_limiter.limit_context_decorator.sleep', but that doesn't work for me because I'm not declaring pyrate_limiter at all. I've tried mocking the time function and then passing that into the LimiterSession, and that sort of works:
worker.py
from requests_ratelimiter import LimiterSession
from time import time
#job('my_queue', connection=redis_conn, timeout=3600, result_ttl=24 * 60 * 60)
def do_work():
session = LimiterSession(per_second=1, time_function=time)
r = session.get(WORK_URL)
test.py
import requests_mock
def test_get(client):
# call the Flask function to kick off the task
client.get("/do_work/")
with patch("my_app.worker.time", return_value=None) as mock_time:
with requests_mock.Mocker() as m:
response_success = {"result": "All good"}
m.get(URL, json=response_success)
worker = SimpleWorker([my_queue], connection=redis_conn)
worker.work(burst=True) # Work until the queue is empty
assert mock_time.call_count == 1
However, then I see time called many more times than sleep would be, so I don't get the info I need from it. And patching my_app.worker.time.sleep results in the error:
AttributeError: does not have the attribute 'sleep'
I have also tried patching the pyrate_limiter as the requests_ratelimiter testing code does:
with patch(
"my_app.worker.requests_ratelimiter.pyrate_limiter.limit_context_decorator.sleep", return_value=None
) as mock_sleep:
But this fails with:
ModuleNotFoundError: No module named 'my_app.worker.requests_ratelimiter'; 'my_app.worker' is not a package
How can I test and make sure the rate limiter is engaging properly?
The solution was indeed to use 'pyrate_limiter.limit_context_decorator.sleep', despite the fact that I wasn't importing it.
When I did that and made the mock return None, I discovered that sleep() was being called tens of thousands of times because it's in a while loop.
So in the end, I also needed to use freezegun and a side effect on my mock_sleep to get the behavior I wanted. Now time is frozen, but sleep() jumps the test clock forward synchronously and instantly by the amount of seconds passed as an argument.
from datetime import timedelta
from unittest.mock import patch
import requests_mock
from freezegun import freeze_time
from rq import SimpleWorker
def test_get(client):
with patch("pyrate_limiter.limit_context_decorator.sleep") as mock_sleep:
with freeze_time() as frozen_time:
# Make sleep operate on the frozen time
# See: https://github.com/spulec/freezegun/issues/47#issuecomment-324442679
mock_sleep.side_effect = lambda seconds: frozen_time.tick(timedelta(seconds=seconds))
with requests_mock.Mocker() as m:
m.get(URL, json=response_success)
worker = SimpleWorker([my_queue], connection=redis_conn)
worker.work(burst=True) # Work until the queue is empty
# The worker will do enough to get rate limited once
assert mock_sleep.call_count == 1
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.
I am trying to create unittests for my flask application which should assert exceptions properly.
I am attaching simplified code sample on what i want to test. The below test should finish as success.
def my_function():
abort(400,"error")
import unittest
from werkzeug import exceptions
class Tests(unittest.TestCase):
def test_event_link(self):
self.assertRaises(exceptions.BadRequest,my_function)
unittest.main(argv=[''], verbosity=2, exit=False)
I would simply patch the Flask abort function and ensure it is called with the correct value, this is preferable as it only tests your code not the behaviour of Flasks abort function which could change with future versions of Flask and break your tests.
See below example based on your code which also includes examples of testing the exception if this is what you would prefer to do.
# code.py
from flask import abort
def my_function():
abort(400, "error")
# test.py
import unittest
from unittest.mock import patch
from werkzeug import exceptions
import code # Your code file code.py
class Tests(unittest.TestCase):
#patch('code.abort')
def test_one(self, mock_abort):
code.my_function()
mock_abort.assert_called_once_with(400, 'error')
def test_two(self):
with self.assertRaises(exceptions.BadRequest):
code.my_function()
def test_three(self):
with self.assertRaisesRegexp(exceptions.BadRequest, '400 Bad Request: error'):
code.my_function()
unittest.main(argv=[''], verbosity=2, exit=False)
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