What I Learned at Work this Week: python patch

Mike Diaz
4 min readMar 19, 2023
Photo by Walls.io : https://www.pexels.com/photo/desk-with-stationary-elements-and-a-sticky-note-with-a-hashtag-on-it-15595153/

It finally happened, we’re writing tests for the product that constantly breaks. It seems like an obvious move, but most readers have probably experienced deprioritizing a security measure in favor of work that will “move the needle.” After all, the script isn’t broken today, but clients are asking us to add features as soon as possible.

But these same clients aren’t happy when we publish a breaking change, so I spent this past week learning about how to use pytest. I attempted to write a very simple test that confirms that a function properly transforms a DB query result into a dictionary. It worked well locally, but when I deployed it in dev, I saw a SQL connection error because my local connection didn’t match dev. A more experienced developer explained to me that this test shouldn’t be making a live query, but should instead patch the results to save time and resources.

The Python Mock Library

So far in this post, I’ve used the word patch, which is a feature of the Mock library. I learned about patch first because that’s what I needed in the moment, but we can start from the beginning here. According to Real Python, “A mock object substitutes and imitates a real object within a testing environment.” In the linked article, we see that mock can be built out to imitate whatever part of json (or whatever else) that we want:

from unittest.mock import Mock
mock = Mock()
print(mock) # <Mock id='4561344720'>

# mock is not an object that we can use in our code
# we can pass it as an argument:
do_something(mock)

# we can also use it to patch an entire library:
json = mock

# we can run functions off our mock
json.loads('{"key": "value"}')

# and we can assert that our function was invoked:
json.loads.assert_called()

patch

When I started writing this post, I thought that mock and patch were the same thing. I realize now that patch has a unique functionality, which is the reason I had to use it in my code at work. Here’s an example:

def complex_function():
# this function can do whatever
# it currently returns nothing
print('we did nothing')

def function_one():
# we will get a specific response if our function returns 'banana'
if complex_function() == 'banana':
return 'the patch worked'
return 'failure'

result = function_one()

print(f'Here is our result: {result}')

For example purposes, we can imagine that complex_function is making an API call or making a big calculation — things we wouldn’t want to happen while we’re testing. Ideally, we’d want to avoid those processes while testing and, according to the same Real Python article, this is where we’d want to use patch: “[patch] looks up an object in a given module and replaces that object with a Mock.” Here’s the code that worked for me:

from unittest.mock import patch
from python_practice import function_one

@patch('python_practice.complex_function', return_value='banana')
def test_function_one(self):
# setup
expected_result = 'the patch worked'

# act
actual_result = function_one()

#verify
assert actual_result == expected_result

We can use patch as a decorator with the @ symbol before our function. The first argument is a stringified import of the function we’re patching and the second in the return_value we’re patching for the function. No matter what it does in practice, our return value for the test will be the string banana.

We set the expectation, run the parent function, and compare them. If you run this code at home (make sure to pip install any missing libraries), you should see a passing test!

An incomplete investigation

In the previous section, I mentioned that we might write a patch when we want to avoid running a costly function during testing. I have to admit that I wrote a hypothetical complex_function because I couldn’t get around one that used actual unwanted logic:

import time

def sleep_function():
time.sleep(10)
return True

If I replace complex_function in my code with sleep_function, my test will still pass, but it’ll take 10 seconds to run. Obviously this is something we want to avoid and should be able to since I believe I got it working at my job. I’m writing about this challenge because I hope I’ll be able to explain what I was missing after I get a few more opinions. It’s also important to share when we struggle, because a polished workflow might set unrealistic expectations for others. If there’s another section after this one, it’s because I figured out what I was doing wrong!

Sources

--

--