11

I'm trying to make a usable tests for my package, but using Flask.test_client is so different from the requests API that I found it hard to use.

I have tried to make requests.adapters.HTTPAdapter wrap the response, but it looks like werkzeug doesn't use httplib (or urllib for that matter) to build it own Response object.

Any idea how it can be done? Reference to existing code will be the best (googling werkzeug + requests doesn't give any useful results)

Many thanks!!

ImportanceOfBeingErnest
  • 251,038
  • 37
  • 461
  • 518
eplaut
  • 624
  • 1
  • 5
  • 23

3 Answers3

1

A PyPI package now exists for this so you can use pip install requests-flask-adapter.

Adam Dangoor
  • 261
  • 1
  • 7
0

As I understood what you need is a mocking (see What is Mocking? and Mock Object). Well, couple of your options listed below:

Community
  • 1
  • 1
Andriy Ivaneyko
  • 15,838
  • 3
  • 43
  • 65
  • thanks, but I want adapter, not a mock. creating a middleware which translates `Flask.test_client` to `requests_mock` sounds an overhead over just creating `requests.Adapter` – eplaut Dec 13 '16 at 15:47
  • For anyone who does want that middleware, there is a PyPI package which provides it: https://pypi.org/project/requests-mock-flask. – Adam Dangoor Jan 20 '20 at 15:03
0

You can use the requests method defined in the code section below. To use it you'll also need to define the get_auth_headers method shown in the code below.

Features:

  • Wrapper around the Flask test client.
  • Automatically set the request headers.

    • Content-Type: Application/json when passing json
    • Basic Authentication constructed when passing auth.
  • Dumps your json data as a string to the Flask test client.
  • (Missing) Wrap the response into a requests.Response object

Code:

class MyTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.client = self.app.test_client()
        self.app_context = self.app.app_context()
        self.app_context.push()
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.app_context.pop()

    def get_auth_headers(self, username, password):
        return {
            'Authorization':
                'Basic ' + base64.b64encode(
                    (username + ':' + password).encode('utf-8')).decode('utf-8'),
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }

    def requests(self, method, url, json={}, auth=(), **kwargs):
        """Wrapper around Flask test client to automatically set headers if JSON
        data is passed + dump JSON data as string."""
        if not hasattr(self.client, method):
            print("Method %s not supported" % method)
            return
        fun = getattr(self.client, method)

        # Set headers
        headers = {}
        if auth:
            username, password = auth
            headers = self.get_auth_headers(username, password)

        # Prepare JSON if needed
        if json:
            import json as _json
            headers.update({'Content-Type': 'application/json'})
            response = fun(url,
                           data=_json.dumps(json),
                           headers=headers,
                           **kwargs)
        else:
            response = fun(url, **kwargs)
        self.assertTrue(response.headers['Content-Type'] == 'application/json')
        return response

Usage (in a test case):

def test_requests(self):
    url = 'http://localhost:5001'
    response = self.requests('get', url)
    response = self.requests('post', url, json={'a': 1, 'b': 2}, auth=('username', 'password'))
    ...

The only difference with requests is that instead of typing requests.get(...) you'll have to type self.request('get', ...).

If you really want requests behaviour, you'll need to define your own get, put, post, delete wrappers around requests

Note:

An optimal way to do this might be to subclass FlaskClient as described in Flask API documentation

JahMyst
  • 1,416
  • 3
  • 15
  • 35