I am writing some tests for a Flask application and am running into an issue where the URLs in my test_client responses do not match those produced by calling url_for from within the test case. For example, I have the following in my template:
<li><a href="#" onClick="requestReport('{{ url_for('report') }}', '{{ session['email'] }}');">Report</a></li>
The response from the test_client renders this as:
<li><a href="#" onClick="requestReport('/api/report', '[email protected]');">Report</a></li>
I have a test case that checks to make sure this URL appears on a page under certain conditions:
self.client = self.app.test_client(use_cookies=True)
with self.app.app_context():
page = self.client.get(url_for("index"), follow_redirects=True).data.decode()
assert url_for("report") in page
The problem is that this test will fail even when the URL appears on the page, because the url_for call in the template is producing different output than the url_for call in my test case. If I print url_for("report") from the test case code, I get:
http://localhost:5000/api/report
I have the SERVER_NAME key in app.config set to "localhost:5000" because if I do not have SERVER_NAME set the test case code will throw this error:
RuntimeError: Application was not able to create a URL adapter for request independent URL generation. You might be able to fix this by setting the SERVER_NAME config variable.
Obviously, I could work around this by hard-coding the URL in my test case code, but I would prefer to use URL_for so that future changes to my URL do not break my test code.
I have tried a couple different strings as SERVER_NAME, including "" which just generates a malformed URL that still doesn't match the one generated in the response.
Is there some way to fix this other than hard-coding the URL?
The url_for() function is very useful for dynamically building a URL for a specific function. The function accepts the name of a function as first argument, and one or more keyword arguments, each corresponding to the variable part of URL.
url_for() uses the route registration to find all routes registered against that name and finds the one best fitting the parameters you also passed to url_for() . what happens when two functions have the same name, but are in different modules? @aitchnyu: then you'll need to give one an explicit endpoint value.
url_for generates a URL to an endpoint using the method passed in as an argument. Note that url_for is typically imported directly from flask instead of from flask. helpers , even though it is defined within the helpers module.
The url_for() function is used to build a URL to the specific function dynamically. The first argument is the name of the specified function, and then we can pass any number of keyword argument corresponding to the variable part of the URL.
If you use url_for
in an application context like you do in your test code Flask automatically assumes you want to create an external link including the full host name (set in the SERVER_NAME
config variable). Whereas the url_for
in your template will create an internal link without the host name. So they won't match. To check for equality you need to explicitly set the _external
property to False
.
self.client = self.app.test_client(use_cookies=True)
with self.app.app_context():
page = self.client.get(url_for("index"), follow_redirects=True).data.decode()
assert url_for("report", _external=False) in page
For API testing, you can use test_request_context
class TestFlaskApi(unittest.TestCase):
def setUp(self):
self.app = create_app()
self.app_context = self.app.test_request_context()
self.app_context.push()
self.client = self.app.test_client()
def test_hello(self):
response = self.client.get(url_for('api.hello'),
content_type='text')
self.assertEqual(response.get_data(as_text=True), 'hello')
The top answer pushed the test request context but didn't pop it in tearDown
method. The proper setup and teardown should like this:
class TestFlaskApi(unittest.TestCase):
def setUp(self):
self.app = create_app()
self.context = self.app.test_request_context()
self.context.push() # push it
self.client = self.app.test_client()
def tearDown(self):
self.context.pop() # pop it
def test_hello(self):
response = self.client.get(url_for('api.hello'), content_type='text')
self.assertEqual(response.get_data(as_text=True), 'hello')
If you are using pytest, check out this answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With