Home > Back-end >  Are there considerations that need made when using Mocking for python unittests when the object bein
Are there considerations that need made when using Mocking for python unittests when the object bein

Time:11-05

I'm not super familiar with mocking, but I have to write unit tests for an endpoint for a flask app. So I'm using flask's unittest tools. We use blueprints, so the endpoints are located in a different file than the flask app. I have a function (call it function A). That function uses a helper function (call it function B). Function A always uses function B, and function B is only ever used by function A. I understand that in a unit test, its best to test one function at a time, but for all intent and purposes, these 2 functions are 2 halves of the same action. They probably should just be one function but it was split for readability.

Function B makes a call to a webservice using the requests library.

I would like to mock the call to this webservice. For some reason, this is giving me trouble. The webservice call isn't being properly captured and mocked. Instead, the actual webservice is being called. However, in my testing earlier when I created a dummy endpoint that made a similar webservice call, the mocking worked properly. This leads me to believe that the problem stems from the webservice call happening in funciton B not function A. Here is the code for the endpoint.

@app.route('/myendpoint', methods=\['POST'\])
    def functionA():
        <some code>
        functionB():
        return something

    def functionB():
        resp = requests.get(url, headers=header)

and here is my test function in another file.

    @patch.object(requests, 'get')
    def test_functionA(self, mock_requests):
        mock_requests.json().returnvalue = json.dumps('"some": "value"')
        mock_requests.status_code = 201
        data = {'some': 'data'}
        headers = {'Authorization': 'Bearer somekey'}
        result = self.app.post('/myendpoint', data=data, follow_redirects=True, content_type='multipart/form-data', headers=headers)
        mock_requests.assert_called()
        self.assertEqual(result.status_code, 201)

In addition to my theory that it is failing because function B is making the request rather than function A, I have a theory that involves imports. In all of the documentation I've read for python unittests, unit testing with flask, and mocking, typically in the examples, the test script is in the same directory as the script being tested. The test script will then import whatever object is being mocked from the script being tested. So if the script being tested has a class CoolObject() and you wanted to mock it, you'd import CoolObject from the script being tested into the testing script. In my case, the file structure is not so straight-forward. Here is the file structure:

myproject
    ⊢---src
    |    ⊢---routes
    |        ⊢---CRUD
    |            ⊢---myscript.py
    ⊢---test_scripts
         ⊢---test_script.py

So imports are a little complicated. So rather than importing requests.get FROM the script being tested, I just imported it like normal as part of the standard library. I fear this may give me undesired results. HOWEVER, I still think it is more likely that the problem is my original theory about the helper function. If the import was really the cause of the problem, the testing I did earlier with a dummy endpoint wouldn't have worked but it did.

I'm at a loss. Can anybody give me some help?

CodePudding user response:

I'll try to answer to your question. I have create 2 files in the paths you have indicated in your question:

  • production file is: src/routes/CRUD/myscript.py
  • test code file is : test_scripts/test_script.py

The content of 2 files are showed below.

test_script.py

import json
import unittest
from unittest import mock
from src.routes.CRUD.myscript import requests, functionA

class TestFuncA(unittest.TestCase):
    @mock.patch.object(requests, 'get')
    def test_functionA(self, mock_requests):
        mock_requests.json().returnvalue = json.dumps('"some": "value"')
        mock_requests.status_code = 201
        data = {'some': 'data'}
        headers = {'Authorization': 'Bearer somekey'}
        #result = self.app.post('/myendpoint', data=data, follow_redirects=True, content_type='multipart/form-data', headers=headers)
        result = functionA()
        mock_requests.assert_called()
        self.assertEqual(result.status_code, 201)

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

As you can see, in the test code I have substitute the call app.post() with the direct call to functionA(). I hope this is not a problem.

Note the import in the file:

from src.routes.CRUD.myscript import requests, functionA

myscript.py

import requests
from flask import Flask

app = Flask(__name__)

def functionB():
    url = "http://www.someurl.com"
    header = ""
    resp = requests.get(url, headers=header)
    resp.status_code = 201
    return resp

@app.route('/myendpoint', methods=['POST'])
def functionA():
    #<some code>
    resp = functionB()
    return resp

In my system the test is executed with success.

If I remove the call to functionB() from the code of functionA() the test fails.

CodePudding user response:

While this may appear somewhat anti-climactic, it turns out that the answer is that mocking doesn't work how I was trying to make it work. When you patch an object or function, you're not catching that object/function, you're catching the reference to it. When I try to catch requests.get, I'm specifically catching and mocking the reference to it inside function A. As it turns out, unittesting doesn't appear to look for the name of that reference outside of the top level function you're testing.

So I do have to split these apart and treat them like proper unit testing.

Test function A and function B independently even though they're so high coupled in this context.

  • Related