Python unit test patch failing - python

So I was following this youtube video:
https://youtu.be/6tNS--WetLI?t=1973
When I replicate what is done in the video, my test fails:
Class:
import requests
class JetpackRebooter:
#staticmethod
def reboot_jetpack(secure_token):
response = requests.post('http://my.jetpack/restarting/reboot/', data={'gSecureToken' : secure_token})
return response.status_code
Unit test:
from src.jetpack_rebooter import JetpackRebooter
from unittest.mock import patch
def test_reboot_jetpack():
secure_token = 'ca26bacf85c6d69d0bdaa3ff07df3cc4118abf45'
with patch("jetpack_rebooter.requests.post") as mocked_post:
mocked_post.return_value.status_code = 200
assert JetpackRebooter.reboot_jetpack(secure_token) == 200
However, changing patch("jetpack_rebooter.requests.post") to patch("requests.post") causes the test to pass. Why is this?

patch("jetpack_rebooter.requests.post") does not work because the module's full import path is not jetpack_rebooter, but src.jetpack_rebooter.
Because of that, patch("src.jetpack_rebooter.requests.post") would work.
However, as src.jetpack_rebooter.requests points to the requests module object, there is no difference between patch("src.jetpack_rebooter.requests.post") and patch("requests.post"); but there would be a difference between patch("src.jetpack_rebooter.requests") and patch("requests").

Related

How to mock an import within an import

So I am trying to test an api lookup with my mock data.
I am testing a method within transform.py that imports a module lookup
import lookup
col = lookup.ColFinder()
url = "xyz"
if col.is_present(url):
do this
lookup.py
import secdata
class ColFinder():
def is_present(url):
if url in secdata.CUSTOM_STUFF:
return secdata.CUSTOM_STUFF[url]
secdata.py
secdata.CUSTOM_STUFF=load("some_file")
I want to mock the JSON (file being loaded within secdata.CUSTOM_STUFF)
I have tried mocking using the unittest.mock with some custom config within tests/resources
CUSTOM_CONFIG = secdata.load(os.path.join(os.path.dirname(__file__),'/resources/custom_config.json'))
import mock
#mock.patch("transform.lookup.secdata.CUSTOM_STUFF" , return_value=CUSTOM_CONFIG)
def test_blah_blah(self, *_):
but this doesn't seem to load the file I am trying to reference. Please can someone help me mock this, point out what am I doing wrong.
Thank you in advance.

Simple Function Mocking in Python

I'm trying to do what I feel like should be a pretty simple mock in python.
I have a tree that looks like this:
- src
- auth
- authenticator.py
- http_client
- auth_client.py
- tests
- test_authenticator.py
My authenticator.py looks like:
import sys
sys.path.append("src/http_client")
import auth_client
def authenticate_request(headers="", env="dev"):
# logic...
auth_client.do_auth(headers)
# logic...
Then auth_client.py looks like:
import json
import requests
def do_auth(headers):
url = "https://FAKE_URL.com"
r = requests.get(url, headers)
return r.json()
And my test_authenticator.py has a test that looks like
import mock
import sys
sys.path.append("src/auth")
sys.path.append("src/http_client")
import authenticator as auth
import auth_client
#mock.patch('auth_client.do_auth')
def test_good(self, mock_request):
headers = FAKE_HEADERS
mock_request.return_value = '{\"response:\": 200}'
body, statusCode = auth.authenticate_request(
headers=headers)
self.assertEqual(200, statusCode)
# self.assertEqual(body, "Token must have kid")
def mock_request():
return "{\"response\":200}"
But I can't seem to actually mock the function. When I run my tests with pytest I get in the coverage that it's hitting auth_client even though I don't want it to.
How do I properly mock this function?
Thanks!

Can not monkeypatch imported module

I have a very simple Google Cloud Function written in Python and it makes a reference to Google's Secret manager via their Python library.
The code is very simple and it looks like this:
import os
from google.cloud import secretmanager
import logging
client = secretmanager.SecretManagerServiceClient()
secret_name = "my-secret"
project_id = os.environ.get('GCP_PROJECT')
resource_name = "projects/{}/secrets/{}/versions/latest".format(project_id, secret_name)
response = client.access_secret_version(resource_name)
secret_string = response.payload.data.decode('UTF-8')
def new_measures_handler(data, context):
logging.info(secret_string)
print('File: {}.'.format(data['name']))
and then I have my simple unit test which is trying to take advantage of monkey patching:
import main
def test_print(capsys, monkeypatch):
# arrange
monkeypatch.setenv("GCP_PROJECT", "TestingUser")
monkeypatch.setattr(secretmanager, "SecretManagerServiceClient", lambda: 1)
name = 'test'
data = {'name': name}
# act
main.new_measures_handler(data, None)
out, err = capsys.readouterr()
#assert
assert out == 'File: {}.\n'.format(name)
Everything goes well with the mock for the environment variable but I can not mock secretmanager. It keeps on trying to call the actual API. My ultimate goal is to mock secretmanager.SecretManagerServiceClient() and make it return an object which later on can be used by: client.access_secret_version(resource_name) (which I will need to mock as well, I think)
See my answer to this question for a working example of using unittest patching and mocking to mock Google API calls and return mock results:
How to Mock a Google API Library with Python 3.7 for Unit Testing

python: How to mock helper method?

Can you please help me out to figure what I did wrong? I have the following unit test for a python lambdas
class Tests(unittest.TestCase):
def setUp(self):
//some setup
#mock.patch('functions.tested_class.requests.get')
#mock.patch('functions.helper_class.get_auth_token')
def test_tested_class(self, mock_auth, mock_get):
mock_get.side_effect = [self.mock_response]
mock_auth.return_value = "some id token"
response = get_xml(self.event, None)
self.assertEqual(response['statusCode'], 200)
The problem is that when I run this code, I get the following error for get_auth_token:
Invalid URL '': No schema supplied. Perhaps you meant http://?
I debugged it, and it doesn't look like I patched it correctly. The Authorization helper file is in the same folder "functions" as the tested class.
EDIT:
In the tested_class I was importing get_auth_token like this:
from functions import helper_class
from functions.helper_class import get_auth_token
...
def get_xml(event, context):
...
response_token = get_auth_token()
After changing to this, it started to work fine
import functions.helper_class
...
def get_xml(event, context):
...
response_token = functions.helper_class.get_auth_token()
I still don't fully understand why though
In your first scenario
in tested_class.py, get_auth_token is imported
from functions.helper_class import get_auth_token
The patch should be exactly the get_auth_token at tested_class
#mock.patch('functions.tested_class.get_auth_token')
Second scenario
With the following usage
response_token = functions.helper_class.get_auth_token()
The only way to patch is this
#mock.patch('functions.helper_class.get_auth_token')
alternative
With import like this in tested_class
from functions import helper_class
helper_class.get_auth_token()
patch could be like this:
#mock.patch('functions.tested_class.helper_class.get_auth_token')
patch() works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work, you must ensure that you patch the name used by the system under test.
The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.
Python documentation has a very good example. where to patch

Unit testing a python app that uses the requests library

I am writing an application that performs REST operations using Kenneth Reitz's requests library and I'm struggling to find a nice way to unit test these applications, because requests provides its methods via module-level methods.
What I want is the ability to synthesize the conversation between the two sides; provide a series of request assertions and responses.
It is in fact a little strange that the library has a blank page about end-user unit testing, while targeting user-friendliness and ease of use. There's however an easy-to-use library by Dropbox, unsurprisingly called responses. Here is its intro post. It says they've failed to employ httpretty, while stating no reason of the fail, and written a library with similar API.
import unittest
import requests
import responses
class TestCase(unittest.TestCase):
#responses.activate
def testExample(self):
responses.add(**{
'method' : responses.GET,
'url' : 'http://example.com/api/123',
'body' : '{"error": "reason"}',
'status' : 404,
'content_type' : 'application/json',
'adding_headers' : {'X-Foo': 'Bar'}
})
response = requests.get('http://example.com/api/123')
self.assertEqual({'error': 'reason'}, response.json())
self.assertEqual(404, response.status_code)
If you use specifically requests try httmock. It's wonderfully simple and elegant:
from httmock import urlmatch, HTTMock
import requests
# define matcher:
#urlmatch(netloc=r'(.*\.)?google\.com$')
def google_mock(url, request):
return 'Feeling lucky, punk?'
# open context to patch
with HTTMock(google_mock):
# call requests
r = requests.get('http://google.com/')
print r.content # 'Feeling lucky, punk?'
If you want something more generic (e.g. to mock any library making http calls) go for httpretty.
Almost as elegant:
import requests
import httpretty
#httpretty.activate
def test_one():
# define your patch:
httpretty.register_uri(httpretty.GET, "http://yipit.com/",
body="Find the best daily deals")
# use!
response = requests.get('http://yipit.com')
assert response.text == "Find the best daily deals"
HTTPretty is far more feature-rich - it offers also mocking status code, streaming responses, rotating responses, dynamic responses (with a callback).
You could use a mocking library such as Mocker to intercept the calls to the requests library and return specified results.
As a very simple example, consider this class which uses the requests library:
class MyReq(object):
def doSomething(self):
r = requests.get('https://api.github.com', auth=('user', 'pass'))
return r.headers['content-type']
Here's a unit test that intercepts the call to requests.get and returns a specified result for testing:
import unittest
import requests
import myreq
from mocker import Mocker, MockerTestCase
class MyReqTests(MockerTestCase):
def testSomething(self):
# Create a mock result for the requests.get call
result = self.mocker.mock()
result.headers
self.mocker.result({'content-type': 'mytest/pass'})
# Use mocker to intercept the call to requests.get
myget = self.mocker.replace("requests.get")
myget('https://api.github.com', auth=('user', 'pass'))
self.mocker.result(result)
self.mocker.replay()
# Now execute my code
r = myreq.MyReq()
v = r.doSomething()
# and verify the results
self.assertEqual(v, 'mytest/pass')
self.mocker.verify()
if __name__ == '__main__':
unittest.main()
When I run this unit test I get the following result:
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Missing from these answers is requests-mock.
From their page:
>>> import requests
>>> import requests_mock
As a context manager:
>>> with requests_mock.mock() as m:
... m.get('http://test.com', text='data')
... requests.get('http://test.com').text
...
'data'
Or as a decorator:
>>> #requests_mock.mock()
... def test_func(m):
... m.get('http://test.com', text='data')
... return requests.get('http://test.com').text
...
>>> test_func()
'data'
using mocker like in srgerg's answer:
def replacer(method, endpoint, json_string):
from mocker import Mocker, ANY, CONTAINS
mocker = Mocker()
result = mocker.mock()
result.json()
mocker.count(1, None)
mocker.result(json_string)
replacement = mocker.replace("requests." + method)
replacement(CONTAINS(endpoint), params=ANY)
self.mocker.result(result)
self.mocker.replay()
For the requests library, this would intercept the request by method and endpoint you're hitting and replace the .json() on the response with the json_string passed in.
If you break out your response handler/parser into a separate function, you can work with requests.Response objects directly, without needing to mock the client-server interaction.
Code under test
from xml.dom import minidom
from requests.models import Response
def function_under_test(s3_response: Response):
doc = minidom.parseString(s3_response.text)
return (
s3_response.status_code,
doc.getElementsByTagName('Code').item(0).firstChild.data,
)
Test code
import unittest
from io import BytesIO
class Test(unittest.TestCase):
def test_it(self):
s3_response = Response()
s3_response.status_code = 404
s3_response.raw = BytesIO(b"""<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>NoSuchKey</Code>
<Message>The resource you requested does not exist</Message>
<Resource>/mybucket/myfoto.jpg</Resource>
<RequestId>4442587FB7D0A2F9</RequestId>
</Error>
""")
parsed_response = function_under_test(s3_response)
self.assertEqual(404, parsed_response[0])
self.assertEqual("NoSuchKey", parsed_response[1])
There's a library for this, if you want to write your test server with Flask: requests-flask-adaptor
You just have to be careful with the order of imports when monkeypatching.

Categories