Not able to mock the method inside flask URL - unit-testing

I written below url in flask
#app_url.route('/createvm', methods=['GET', 'POST'], defaults={'buildid': None})
def form(buildid):
command = prepare_ansible_command(data)
success,reason = run_command(command)
# here run_coomand method not returning mock return value.
....
writing below unit test case
#patch('app.vm_create.utility.run_command')
def test_vm_create_negative3(self, run_command_mock):
run_command_mock.return_value = True, "response123456"
from app.vm_create.utility import run_command
#I checked run_command here it's returning mock return value (True, "response123456")
with self.client:
resp = self.client.post("/signin/", data={"username": self.act.username,
"password": self.password, "token":True})
resp= self.client.post("/createvm", data=data)
The run_commnad returning mocked return value inside test method.
It's not returning mock return value inside view function (createvm)
running the above test using pytest
pytest test_app.py -k "test_vm_create_negative3"

Solved! This is what it was happening: run_command() function was implemented in app.vm_create.utility.py, but it was imported by app.vm_create.controllers.py. When I tried to mock run_command by patching app.vm_create.utility.run_command (in utility where it was implemented), it didn't work. However, this worked:
#patch("app.vm_create.controllers.run_command")
def test_etc:
etc
As you can see, I'm still patching run_command(), but this time, with the path of the module which is importing it, controllers.

Related

Django: How to mock a method called in an api test?

I keep hitting a method scan_file() that should be mocked. It happens while calling self.client.post() in a django api test. The app setup is below, I've tried to mock the imported scan_file patch("myapp.views.scan_file") as well as the source location patch("myapp.utils.scan_file") neither work.
# myapp.views.py
from myapp.utils import scan_file
class MyViewset():
def scan():
scan_file(file) # <- this should be mocked but its entering the code
#myapp.utils.py
def scan_file(file) -> bool:
boolean_result = call_api(file)
return boolean_result
#test_api.py
class MyAppTest():
def test_scan_endpoint(self):
patcher = patch("myapp.views.scan_file")
MockedScan = patcher.start()
MockedScan.return_value = True
# This post hits the scan_file code during the api
# call but it should be mocked.
resp = self.client.post(
SCAN_ENDPOINT,
data={
"file" :self.text_file,
"field1" : "Progress"
}
)
I've also tried the following syntax for mocking, and tried including it in the test setup() as well:
self.patcher = patch("myapp.views.scan_file")
self.MockedScan = self.patcher.start()
self.MockedScan.return_value = True

Mock an internal object call in a Flask view - with pytest

Based on the following example:
app = Flask(__name__)
#app.route('/users')
def get_users():
return UsersAPI().get_users()
And the following tests (using pytest and pytest-mock):
#pytest.fixture
def users():
return UsersAPI(how_many=1)
def test_simple(users, mocker):
mocker.patch("???", return_value=users)
I simply want to call UsersAPI(how_many=1) instead of UsersAPI(). Is this possible to do?
(if you know how to get done with unittest.mock that is also fine since pytest-mock is simply some pytest wrapper)
Turns out it is as easy as:
#pytest.fixture
def users():
return UsersAPI(how_many=1)
def test_simple(users, mocker):
mocker.patch("path.to.module.UsersAPI", return_value=users)
And this also works:
mocker.patch.object(path.to.module, 'UsersAPI', return_value=users)

Why does mock patch only work when running specific test and not whole test suite?

I'm using Django and Pytest specifically to run the test suite and am trying to test that a specific form shows up with expected data when a user hits the site (integration test).
This particular view uses a stored procedure, which I am mocking since the test would never have access to that.
My test code looks like this:
#test_integrations.py
from my_app.tests.data_setup import setup_data, setup_sb7_data
from unittest.mock import patch
...
# Setup to use a non-headless browser so we can see whats happening for debugging
#pytest.mark.usefixtures("standard_browser")
class SeniorPageTestCase(StaticLiveServerTestCase):
"""
These tests surround the senior form
"""
#classmethod
def setUpClass(cls):
cls.host = socket.gethostbyname(socket.gethostname())
super(SeniorPageTestCase, cls).setUpClass()
def setUp(self):
# setup the dummy data - this works fine
basic_setup(self)
# setup the 'results'
self.sb7_mock_data = setup_sb7_data(self)
#patch("my_app.utils.get_employee_sb7_data")
def test_senior_form_displays(self, mock_sb7_get):
# login the dummy user we created
login_user(self, "futureuser")
# setup the results
mock_sb7_get.return_value = self.sb7_mock_data
# hit the page for the form
self.browser.get(self.live_server_url + "/my_app/senior")
form_id = "SeniorForm"
# assert that the form displays on the page
self.assertTrue(self.browser.find_element_by_id(form_id))
# utils.py
from django.conf import settings
from django.db import connections
def get_employee_sb7_data(db_name, user_number, window):
"""
Executes the stored procedure for getting employee data
Args:
user_number: Takes the user_number
db (db connection): Takes a string of the DB to connect to
Returns:
"""
cursor = connections[db_name].cursor()
cursor.execute(
'exec sp_sb7 %s, "%s"' % (user_number, window.senior_close)
)
columns = [col[0] for col in cursor.description]
results = [dict(zip(columns, row)) for row in cursor.fetchall()]
return results
# views.py
from myapp.utils import (
get_employee_sb7_data,
)
...
###### Senior ######
#login_required
#group_required("user_senior")
def senior(request):
# Additional Logic / Getting Other Models here
# Execute stored procedure to get data for user
user_number = request.user.user_no
results = get_employee_sb7_data("production_db", user_number, window)
if not results:
return render(request, "users/senior_not_required.html")
# Additional view stuff
return render(
request,
"users/senior.html",
{
"data": data,
"form": form,
"results": results,
},
)
If I run this test itself with:
pytest my_app/tests/test_integrations.py::SeniorPageTestCase
The tests pass without issue. The browser shows up - the form shows up with the dummy data as we would expect and it all works.
However, if I run:
pytest my_app
All other tests run and pass - but all the tests in this class fail because it's not patching the function.
It tries to call the actual stored procedure (which fails because it's not on the production server yet) and it fails.
Why would it patch correctly when I call that TestCase specifically - but not patch correctly when I just run pytest on the app or project level?
I'm at a loss and not sure how to debug this very well. Any help is appreciated
So what's happening is that your views are imported before you're patching.
Let's first see the working case:
pytest imports the test_integrations file
the test is executed and patch decorator's inner function is run
there is no import of the utils yet and so patch imports and replaces the function
test body is executed, which passes a url to the test client
the test client imports the resolver and in turn it imports the views, which imports the utils.
Since the utils are already patched, everything works fine
If another test case runs first, that also imports the same views, then that import wins and patch cannot replace the import.
Your solution is to reference the same symbol. So in test_integrations.py:
#patch("myapp.views.get_employee_sb7_data")

how to mock a method decorated with Python Flask route()

I need to unit test whether a method decorated by a Flask route() gets called or not.
I'd like to do this without modifying the original code under test, if possible, so mocking the method would suite my requirements perfectly.
Hence I am asking this specific question about how to mock a decorated request method (I want to stress this to try to avoid people wasting their time with less specific answers)...
Sample application jflask.py:
from flask import Flask
app = Flask(__name__)
app.config.from_object(__name__)
#app.route('/hello') # This method represents the code under test.
def hello(): # I want to assert that this method gets
return 'Hello, World' # called without modifying this code.
if __name__ == "__main__":
app.run()
In the unit test I'm using #patch() to mock the method so I can assert it was called, but the assertion fails. I.e. the mock method doesn't get called, when I expect it to.
Sample unit test test_hello.py:
import unittest
import jflask
from unittest.mock import patch
class jTest(unittest.TestCase):
def setUp(self):
#jflask.app.testing = True
self.app = jflask.app.test_client()
#patch('jflask.hello') # mock the hello() method
def test_hello(self, mock_method):
rv = self.app.get('/hello')
mock_method.assert_called() # this assertion fails
What am I doing wrong ?
Background
Some background information about the actual behaviour I'm trying to test
(since the above is just a condensed test case, and may not seem entirely sane by itself).
In the actual code I am unit testing, there is a before_request() handler
installed for the app. This gets called by Flask before each request is handled, and in
certain situations this handler has been designed to return a response value, which
causes Flask request processing to stop (in this application's case, this feature is used to centrally validate request parameters), so that the usual routed request handler will (deliberately) not get called.
My unit tests need to assert that request processing gets stopped
or continues, appropriately depending on the situation.
Hence, my test needs to mock the real request handler and assert whether
it was called or not.
This is a little hacky but you could inject a logger.
#app.route(...):
def hello(logger=None):
logger = logger or self.logger
logger.info(...)
return ...
def test_...(self):
logger = MagicMock()
self.app.get(logger)
self.assertTrue(logger.info.called)
from functools import wraps
import logging
from datetime import datetime
logging.basicConfig(filename=datetime.now().strftime('%d_%m_%Y.log'),level=logging.INFO)
def logger_required(f):
#wraps(f)
def decorated(*args, **kwargs):
logging.info(f.__name__ + ' was called')
return f(*args, **kwargs)
return decorated
#app.route('/hello')
#logger_required
def hello(): # I want to assert that this gets called
return 'Hello, World'

HTML report for django tests

I have a Django project containing an API (created with rest framework if that counts anywhere). I have added some tests for the API but in order to have an overall view of the tests, either passing, either failing or missing, I need to create an HTML report.
When the tests are finished a HTML table report should be generated which shows the endpoints and HTTP responses covered during tests, the results of the tests plus the combinations which are missing the tests.
Unfortunately I cannot understand how should I do that. I know that coverage can give me a detailed html report, but that's not what I need, I need something like this:
| Endpoint description | 200 | 400 | 403 | 404 |
| GET /endpoint1 | PASS | PASS |PASS | N/A |
| POST /endpoint1 | PASS | FAIL |MISSING| N/A |
Does anybody has any idea about that? Maybe some libs that could help out with that or what strategy should I use for that?
Thank you in advance
Late to the party, but this is my solution to outputting a HTML test report for Django tests. (based on HtmlTestRunner cannot be directly used with Django DiscoverRunner)
The following classes if placed in tests/html_test_reporter.py can be used as a DiscoverRunner which is patched to use HTMLTestRunner.
from django.test.runner import DiscoverRunner
from HtmlTestRunner import HTMLTestRunner
class MyHTMLTestRunner(HTMLTestRunner):
def __init__(self, **kwargs):
# Pass any required options to HTMLTestRunner
super().__init__(combine_reports=True, report_name='all_tests', add_timestamp=False, **kwargs)
class HtmlTestReporter(DiscoverRunner):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Patch over the test_runner in the super class.
html_test_runner = MyHTMLTestRunner
self.test_runner=html_test_runner
Then this is run with:
python manage.py test -v 2 --testrunner tests.html_test_reporter.HtmlTestReporter
By default Django projects use django.test.runner.DiscoverRunner to search for tests and then use PyTest to run them. HTMLTestRunner can be used with PyTest to output a HTML test report, but it does seem possible to configure PyTest to use HTMLRunner through DiscoverRunner.
Hope this helps.
As Django uses the python's standard unittest library, you'll have to tweak some of its parts.
First, you'll need some way to specify which tests actually test which endpoint. A custom decorator is handy for that:
from functools import wraps
def endpoint(path, code):
"""
Mark some test as one which tests specific endpoint.
"""
def inner(func):
#wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
inner._endpoint_path = path
inner._endpoint_code = code
return inner
class MyTestCase(TestCase):
#endpoint(path='/path/one', code=200)
def test_my_path_is_ok(self):
response = self.client.get('/path/one?foo=bar')
self.assertEqual(response.status_code, 200)
#endpoint(path='/path/one', code=404)
def test_my_path_expected_errors(self):
response = self.client.get('/path/one?foo=qux')
self.assertEqual(response.status_code, 404)
def test_some_other_stuff(self):
# this one will not be included in our results grid.
pass
You could use a "magical" approach (e.g. special methods' names to guess the endpoint they are testing) instead, but explicit is better than implicit, right?
Then, you need a way to collect the results of your tests - specifically, of that which test the endpoints. Here we make a (very draft) subclass of unittest.TestResult to handle it:
class EndpointsTestResult(TestResult):
def __init__(self):
super(EndpointsTestResult, self).__init__()
self.endpoint_results = {}
def addError(self, test, err):
super(EndpointsTestResult, self).addError(test, err)
if hasattr(test, '_endpoint_path'):
branch = self.endpoint_results.setdefault(getattr(test, '_endpoint_path'), {})
branch[getattr(test, '_endpoint_code')] = 'MISSING'
def addFailure(self, test, err):
# similar as addError()
def addSuccess(self, test):
# similar as addError()
Finally it's time to actually output our results. Let's make a sublass of the unittest.TextTestRunner and specify it in our custom runner:
class EndpointsTestRunner(TextTestRunner):
def _makeResult(self):
self._result = EndpointsTestResult()
return self._result
def run(self, test):
super(EndpointsTestRunner).run(test)
# After running a test, print out the table
generate_a_nifty_table(self._result.endpoint_results)
class EndpointsDjangoRunner(django.test.runner.DiscoverRunner):
test_runner = EndpointsTestRunner
Now we have our custom EndpointsDjangoRunner, and we should specify it in the settings.py:
TEST_RUNNER = 'path.to.the.EndpointsDjangoRunner'
That's it. Please let me know if you spot any awkward errors in the code.