Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using url_for in tests

Tags:

python

flask

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?

like image 563
Kevin Schellenberg Avatar asked Dec 09 '16 17:12

Kevin Schellenberg


People also ask

What is url_for used for?

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.

How url_ for works?

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.

What does url_ for return?

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.

How do I give a Flask a URL?

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.


3 Answers

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
like image 159
MrLeeh Avatar answered Oct 21 '22 01:10

MrLeeh


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') 
like image 30
pppk520 Avatar answered Oct 21 '22 01:10

pppk520


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.

like image 37
Grey Li Avatar answered Oct 21 '22 02:10

Grey Li