Carmellose
Carmellose

Reputation: 5088

Flask test client and unittest: mocking global object?

I have a flask application which uses a global object data_loader.

The main flask file (let's call it main.py) starts as follow:

app = Flask('my app')
...
data_loader = DataLoader(...)

Later on, this global data_loader object is called in the route methods of the webserver:

class MyClass(Resource):
    def get(self):
      data_loader.load_some_data()
      # ... process data, etc

Using unittest, I want to be able to patch the load_some_data() method. I'm using the flask test_client:

from my_module.main import app

class MyTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        cls.client = app.test_client('my test client')

How can I patch the data_loader method in subsequent tests in MyTest? I have tried this approach, but it does not work (although the data_loader seems to be replaced at some point):

 @unittest.mock.patch('my_module.main.DataLoader')
 def my_test(self, DataLoaderMock):
     
     data_loader = DataLoaderMock.return_value
     data_loader.my_method.return_value = 'new results (patched)'

     with app.test_client() as client:
         response = client.get(f'/some/http/get/request/to/MyTest/route', 
                               query_string={...})

     # ... some assertions to be tested ...

It seems the data_loader is never truly replaced in the Flask app.

Also, is this considered "good practice" to have a global variable in the Flask server, or is the app supposed to have it stored inside?

Thanks

Upvotes: 1

Views: 1712

Answers (1)

Danila Ganchar
Danila Ganchar

Reputation: 11223

About mocking, patch.object can be used to modify object attributes:

@unittest.mock.patch.object(data_loader, 'my_method')
def my_test(self, my_method_mock):
    my_method_mock.return_value = 'new results (patched)'
    with app.test_client() as client:
        response = client.get(f'/some/http/get/request/to/MyTest/route', 
                              query_string={...})

        my_method_mock.assert_called() # ok!

My solution with interesting insights would be:

import unittest
from unittest.mock import patch


class MyTest(unittest.TestCase):
    def test_get(self):
        client = app.test_client('my test client')
        patcher = patch('{package_here}.{module_here}.DataLoader.load_some_data', return_value={'status': 1})
        patcher.start()
        self.assertDictEqual(client.get('/').json, {'status': 1})
        patcher.stop()
        # or
        with patch('{package_here}.{module_here}.DataLoader.load_some_data', return_value={'status': 1}):
            self.assertDictEqual(client.get('/').json, {'status': 1})

About "good practice" and global variables. Yes, I have seen global variables in various projects. But I don't recommend using global variables. Because:

  • It can lead to recursive imports and dependency hell. I have worked with large Flask application with recursive imports. It is really pain. And you can't fix all problems for a short time.
  • Let's imagine you have a tests which mocking a global variables. I think refactoring is more difficult when you have a rather big service.
  • Separate imports and initialization is really simpler and more configurable. In this case all works in one direction import all dependencies -> load config -> initialization -> run. In other case you will have import -> new instance -> new instance -> import -> ....
  • Another reason for memory leaks.

Maybe global variables is not bad way for a stand alone packages, modules etc but not for a project. I also want to recommend using some additional tools. This will not only make it easier to write tests, but it will also save you headaches.

Upvotes: 1

Related Questions