6

I am quite new to unit testing and relatively new to RESTful API development as well. I am wondering how to do unit test for functions inside Resource class in flask restful? I can do unit test for the endpoint's response but I don't know how to do testing for the individual functions inside the endpoint's controller class.

Below is my application code. It has 3 files including test:

  • api.py
  • controller_foo.py
  • test_controller_foo.py
# api.py

from flask import Flask
from flask_restful import Api

from .controller_foo import ControllerFoo


def create_app(config=None):
    app = Flask(__name__)
    app.config['ENV'] ='development'
    return app

application = app = create_app()
api = Api(app)

api.add_resource(ControllerFoo, '/ctrl')

if __name__ == "__main__":
    app.run(debug=True)
# controller_foo.py

from flask_restful import Resource
from flask import request

class ControllerFoo(Resource):
    """
    basically flask-restful's Resource method is a wrapper for flask's MethodView
    """
    def post(self):
        request_data = self.handle_request()
        response = self.process_request(request_data)
        return response

    def handle_request(self):
        json = request.get_json()
        return json

    def process_request(self, data):
         # do some stuffs here
         return {'foo': 'bar'}

I am using unittest

# test_controller_foo.py

import unittest

from api import app
from .controller_foo import ControllerFoo

# initiating class to try testing but I don't know how to start
ctrl = ControllerFoo()

class ControllerFooTestCase(unittest.TestCase):
    def setUp(self):
        self.app = app
        self.app.config['TESTING'] = True
        self.client = app.test_client()
        self.payload = {'its': 'empty'}

    def tearDown(self):
        pass

    def test_get_response(self):
        response = self.client.post('/ctrl', json=self.payload)
        expected_resp = {
            'foo': 'bar'
        }
        self.assertEqual(response.status_code, 200)
        self.assertDictEqual(response.get_json(), expected_resp)

if __name__ == "__main__":
    unittest.main()

I want to know how to properly do unit test for handle_request and process_request function

EDIT: Fixing out my buggy code. Thanks Laurent LAPORTE for the highlights.

addicted
  • 2,901
  • 3
  • 28
  • 49
  • 1
    not sure why it is downvoted. If it is not a valid question, kindly let me know. – addicted Apr 12 '20 at 14:32
  • Just wanna update that I manage to get an answer to my own question. To do unit test for `handle_request` and `process_request` function, I can simulate a request call using ```with self.app.test_request_context('/ctrl'): # code here ``` To test functions inside my ControllerFoo's class, I can create the instance of the class then just call the function actually. For now this method serves my needs. – addicted Apr 16 '20 at 15:43

1 Answers1

5

There are several bugs in your code, so this is not easy to explain.

First of all, the recommended way to do testing with Flask (and Flask-Restful) is to use PyTest instead of unittest, because it is easier to setup and use.

Take a look at the documentation: Testing Flask Applications.

But, you can start with unittest…

note: you can have a confusion with your app module and the app instance in that module. So, to avoid it, I imported the module. Another good practice is to name your test module against the tested module: "app.py" => "test_app.py". You can also have a confusion with the controller module and the controller instance. The best practice is to use a more precise name, like "controller_foo" or something else…

Here is a working unit test:

# test_app.py

import unittest

import app


class ControllerTestCase(unittest.TestCase):
    def setUp(self):
        self.app = app.app
        self.app.config['TESTING'] = True
        self.client = self.app.test_client()
        self.payload = {'its': 'empty'}

    def test_get_response(self):
        response = self.client.post('/ctrl', json=self.payload)
        expected_resp = {'foo': 'bar'}
        self.assertEqual(response.status_code, 200)
        self.assertDictEqual(response.get_json(), expected_resp)


if __name__ == "__main__":
    unittest.main()

As you can see, I also fixed the posted URL, in your application, the URL is "/ctrl", not "controller".

At this point, the test can run, but you have another error:

Ran 1 test in 0.006s

FAILED (errors=1)

Error
Traceback (most recent call last):
  ...
TypeError: process_request() takes 1 positional argument but 2 were given

If you take a look at your process_request() method, you can see that you missed the self parameter. Change it like this.

    def process_request(self, data):
        # do some stuffs here
        return {'foo': 'bar'}

Your test should pass.

But, that not the right way to implement Flask-Restful controolers. Read the doc and use get and post methods…

Laurent LAPORTE
  • 21,958
  • 6
  • 58
  • 103
  • ah you're right, I am missing quite a number of details there. I am rewriting the code in SO to mask out my real code and probably missing out details along the way. Thanks for pointing that out! – addicted Apr 12 '20 at 15:28
  • Aside from the errors, I still want to do unit test for the helper functions in that ControllerFoo class. I read the [doc](https://flask-restful.readthedocs.io/en/latest/quickstart.html) here and it does not have example about abstracting the steps inside the HTTP methods. I have a lot more logic inside the method and I want to use functions as layers of abstraction. – addicted Apr 12 '20 at 15:41