Pytest Patch: Different Mock is used - python-3.x

I am trying to patch an imported class called BotoAWSRequestsAuth, as I want to assert a call that uses the auth.
When I patch the imported class and then try to use the patch in my assertion is is failing because a different magic mock is used, I cant seem to figure out why this is happening.
There error I get is:
AssertionError: expected call not found.
Expected: get('https://x/x/x/x/x', auth=<MagicMock name='BotoAWSRequestsAuth' id='140061268160816'>)
Actual: get('x/x/x/x/x', auth=<MagicMock name='BotoAWSRequestsAuth()' id='140061270695792'>)
.....
Differing items:
{'auth': <MagicMock name='BotoAWSRequestsAuth()' id='140061268160816'>} != {'auth': <MagicMock name='BotoAWSRequestsAuth' id='140061270695792'>}
I have tried to create an extracted and minimal version of my code:
In the module that uses the auth:
from aws_requests_auth.boto_utils import BotoAWSRequestsAuth
def call_api(url):
auth = BotoAWSRequestsAuth(
aws_host = "host",
aws_region = 'eu-west-1',
aws_service = 'execute-api'
)
r = requests.get(url, auth=auth)
data = r.content
And in the test:
with patch(
"my_module.BotoAWSRequestsAuth"
) as mock_requests_auth:
my_module.call_api(url)
mocked_requests.get.assert_called_once_with(
url,
auth=mock_requests_auth
)
Is it obvious what I am doing wrong? Maybe I am not actually mocking the constructor call at all because the actual object looks like BotoAWSRequestsAuth() in the assertion error.

Related

Using hydra.main on main method

Summary of the problem:
I'm running a requests call on an API endpoint, whose request params are hidden in a config file and I decided to try out hydra to retrieve those params [Reason being the request params do change as I'm working on collecting custom dataset using RapidAPI]
I have created a class called QueryParamsLocations which implements the getter methods to fetch the parameters to be later used by run_query method.
class QueryParamsLocations(QueryParams):
#hydra.main(config_path='configs', config_name='location_query')
def get_params_query_string(self, cfg: DictConfig) -> dict():
return {
'query': cfg.location_params.query,
'locale': cfg.location_params.locale,
'currency': cfg.location_params.currency
}
#hydra.main(config_path='configs', config_name='location_query')
def get_url(self, cfg: DictConfig) -> str():
return cfg.urls.location_url
#hydra.main(config_path='configs', config_name='location_query')
def get_headers(self, cfg: DictConfig) -> dict():
return {
'X-RapidAPI-Host': cfg.headers.x_rapidapi_host,
'X-RapidAPI-Key': cfg.headers.x_rapidapi_key
}
class QueryParams is an abstract class which has these 3 getter templates. run_query method is an external call to run the request.
#hydra.main(config_path='configs', config_name='location_query')
def run_query(cfg: DictConfig) -> None:
try:
LoggerFactory.get_logger('logs/logger.log', 'INFO').info('Running query for location')
qpl = QueryParamsLocations()
response = requests.request("GET", qpl.get_url(cfg), headers=qpl.get_headers(cfg), params=qpl.get_params_query_string(cfg))
print(response.json())
except Exception as e:
LoggerFactory.get_logger('logs/logger.log',
'ERROR').error(f'Error in running query: {e}')
run_query()
While running run_query without if name == 'main': and with it as well , the following error is encountered :
[2022-05-16 13:43:32,614][logs/logger.log][ERROR] - Error in running query: **decorated_main()** takes from 0 to 1 positional arguments but 2 were given
Although newer version of hydra (I'm using hydra-core==1.1.2) uses two arguments while creating cfg object however , I'm not sure as to whether there's other way of handling this as such.
Also, by searching through other threads, following was also tried - Compose API
however, from the docs, it requires an override parameter , which is not needed atm.
Would like to know if any other approach can be tried out. Happy to provide more details if needed.
Definitely use the Compose API and not hydra.main() for this use case.
You can just pass an empty array for your override list if you have nothing to override.

How to mock a (PyActiveResource) pyactiveresource.connection UnauthorizedAccess response return, using Django TestCase?

I need to create a unit test that mock a REST API failure call, with a side effect of returning an UnauthorizedAccess exception, from the PyActiveResource project (https://github.com/Shopify/pyactiveresource) and store it in the DB. What I've create so far worked and I've got the desired returned side effect. Then, I catch it on the function foo.function_that_call_myfuncion() which looks like this
my_func.py:
from pyactiveresource.connection import UnauthorizedAccess
class MyFuncAnyThing:
...
def function_that_call_myfuncion(self, attr=None):
try:
module.MyTestClass.myfunction(attr)
except UnauthorizedAccess as error:
#Catch the error response object and store it on DB
resp_body = error.response.body
resp_code = error.response.code
#store it on DB
...
And my test file looks like this
unit_test.py:
from pyactiveresource.connection import UnauthorizedAccess
class TestFoo(TestCase):
def test_exception_unauthorized_access(self):
foo = SomeThingFactory()
with patch('module.MyTestClass.myfunction', side_effect=UnauthorizedAccess()):
foo.function_that_call_myfuncion()
#assertions goes below here
...
So, when the execution reached the try block on function_that_call_myfuncion from my_func.py module, the mock function return the desired exception (UnauthorizedAccess) and the object returned looks like this:
error
UnauthorizedAccess('Response(code=None, body="", headers={}, msg="")')
My problems begins when I try to mock the Response body returned on the UnauthorizedAccess exception. This is what I'm doing:
unit_test.py:
from pyactiveresource.connection import UnauthorizedAccess
class TestFoo(TestCase):
def test_exception_unauthorized_access(self):
foo = SomeThingFactory()
bar = MagicMock()
bar.code = 401
bar.body = '{"errors": "Login or wrong password"}'
with patch('module.MyTestClass.myfunction', side_effect=UnauthorizedAccess(bar)):
foo.function_that_call_myfuncion()
#assertions goes below here
...
And that's is how the mocked object looks like:
error
UnauthorizedAccess('Response(code=401, body="<MagicMock name=\'mock.read()\' id=\'2243840764512\'>", headers={}, msg="<MagicMock name=\'mock.msg\' id=\'2243840808464\'>")')
Note that the code attribute on Response is 401, but the body is empty, even though I've set it here bar.body = '{"errors": "Login or wrong password"}'. I also tried to create a Response object and pass it as parameter on the constructor for UnauthorizedAccess class, which is a subclass of
class ConnectionError(Error): of the pyactiveresource.connection lib code (https://github.com/Shopify/pyactiveresource/blob/e609d844ebace603f74bc5f0a67e9eafe7fb25e1/pyactiveresource/connection.py#L34)
unit_test.py:
from pyactiveresource.connection import UnauthorizedAccess, Response
class TestFoo(TestCase):
def test_exception_unauthorized_access(self):
foo = SomeThingFactory()
resp = Response(code=401,body='{"errors": "Login or wrong password"}')
with patch('module.MyTestClass.myfunction', side_effect=UnauthorizedAccess(response=resp)):
foo.function_that_call_myfuncion()
#assertions goes below here
...
But then I got this error from the Class Response:
#classmethod
def from_httpresponse(cls, response):
"""Create a Response object based on an httplib.HTTPResponse object.
Args:
response: An httplib.HTTPResponse object.
Returns:
A Response object.
"""
> return cls(response.code, response.read(),
dict(response.headers), response.msg, response)
E AttributeError: 'Response' object has no attribute 'read'
What am I missing? I just couldn't figure out how to set the 'read' attribute on the constructor, so that I can get the body value.
I'm using Python 3.8, Django 2.2
I managed to mock Shopify's ClientError exceptions by doing something along the lines of:
import urllib.error
from io import BytesIO
import pyactiveresource.testing.http_fake
pyactiveresource.testing.http_fake.initialize()
response = urllib.error.HTTPError('', 401, '', {}, BytesIO(b''))
pyactiveresource.testing.http_fake.TestHandler.set_response(response)
Which I learned about by digging into the Shopify/pyactiveresource tests.

How to mock multiple urls in request mock

I have a method which is calling two different end points and validating there response.
def foo_bar:
status_1 = requests.post(
"http://myapi/test/status1", {},
headers=headers)
status_2 = requests.post(
"http://myapi/test/status2", {},
headers=headers)
# and check the responses ...
I want to mock the both the url in pytest like this:
def foo_test:
with requests_mock.Mocker() as m1:
m1.post('http://myapi/test/status1',
json={},
headers={'x-api-key': my_api_key})
m1.post('http://myapi/test/status2',
json={},
headers={'x-api-key': my_api_key})
It always throws the error
**NO mock address: http://myapi/test/status2**
seems like its only mocking first url.
So is there any way to mock more than one url in one method?
Yes there is. From the docs: "There is a special symbol at requests_mock.ANY which acts as the wildcard to match anything. It can be used as a replace for the method and/or the URL."
import requests_mock
with requests_mock.Mocker() as rm:
rm.post(requests_mock.ANY, text='resp')
I am not sure if this is the best way but it works for me. You can assert afterwards which URLs were called with:
urls = [r._request.url, for r in rm._adapter.request_history]
I think you have something else going on, it's very normal to mock out a single path like this at a time so that you can return different values from different paths simply. Your example works for me:
import requests
import requests_mock
with requests_mock.Mocker() as m1:
my_api_key = 'key'
m1.post('http://myapi/test/status1',
json={},
headers={'x-api-key': my_api_key})
m1.post('http://myapi/test/status2',
json={},
headers={'x-api-key': my_api_key})
headers = {'a': 'b'}
status_1 = requests.post("http://myapi/test/status1", {}, headers=headers)
status_2 = requests.post("http://myapi/test/status2", {}, headers=headers)
assert status_1.status_code == 200
assert status_2.status_code == 200
Yes, there is a way!
You need to use additional_matcher callback (see docs) and requests_mock.ANY as URL.
Your example (with context manager)
import requests
import requests_mock
headers = {'key': 'val', 'another': 'header'}
def my_matcher(request):
url = request.url
mocked_urls = [
"http://myapi/test/status1",
"http://myapi/test/status2",
]
return url in mocked_urls # True or False
# as Context manager
with requests_mock.Mocker() as m1:
m1.post(
requests_mock.ANY, # Mock any URL before matching
additional_matcher=my_matcher, # Mock only matched
json={},
headers=headers,
)
r = requests.post('http://myapi/test/status1')
print(f"{r.text} | {r.headers}")
r = requests.post('http://myapi/test/status2')
print(f"{r.text} | {r.headers}")
# r = requests.get('http://myapi/test/status3').text # 'NoMockAddress' exception
Adaptation for pytest
Note: import requests_mock library with alias (because requests_mock is a fixture in pytest tests)
See example for pytest framework, GET method and your URLs:
# test_some_module.py
import requests
import requests_mock as req_mock
def my_matcher(request):
url = request.url
mocked_urls = [
"http://myapi/test/status1",
"http://myapi/test/status2",
]
return url in mocked_urls # True or False
def test_mocking_several_urls(requests_mock): # 'requests_mock' is fixture here
requests_mock.get(
req_mock.ANY, # Mock any URL before matching
additional_matcher=my_matcher, # Mock only matched
text="Some fake response for all matched URLs",
)
... Do your requests ...
# GET URL#1 -> response "Some fake response for all matched URLs"
# GET URL#2 -> response "Some fake response for all matched URLs"
# GET URL#N -> Raised exception 'NoMockAddress'

How to get testStep responseAsXml in groovyScript

Concerning soapUI and groovy, I'm trying to get assertion (working) and response both in XML into a variable. I get the error
groovy.lang.MissingMethodException: No signature of method: com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep.getResponseAsXml() is applicable for argument types: () values: [] error at line: 6
I have tried adding import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep but still cant figure it. I did another attempt with message exchange, also to no avail - from what i understand you can't actually use messageExchange in this particular instance
import com.eviware.soapui.model.testsuite.Assertable.AssertionStatus
def TestCase = testRunner.getTestCase()
def StepList = TestCase.getTestStepList()
StepList.each
{
if(it.metaClass.hasProperty(it,'assertionStatus'))
{
if(it.assertionStatus == AssertionStatus.FAILED)
{
def ass = it.getAssertableContentAsXml()
def res = it.getResponseContentAsXml()
log.error "${it.name} " + "${it.assertionStatus}"
log.info ass + res
}
}
}
If you want to get the response from com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep, a possible way is first get the testStep from this class using getTestStep() method.
This method returns a object of class com.eviware.soapui.model.testsuite.TestStep, from this object you can get the testSteps properties like request, response, endpoint... using getPropertyValue(java.lang.string) method.
So in your case to get the response use:
def res = it.getTestStep().getPropertyValue('Response')
instead of:
def res = it.getResponseContentAsXml()
As #tim_yates comments the exception description in this case it's pretty clear, so please take a look at the SOAPUI api and at the links provided in the answer for the next time :).
Hope this helps,

Rauth with google provider and python3

I've got some problem using the rauth library with flask, python3 and google oauth, python2 works without problems.
It says TypeError: the JSON object must be str, not 'bytes'
Here is the log error:
I've found that Issue here and tried to convert the byte to string
decoder=lambda b: json.loads(str(b))
but without success.
Here is my Implementation
class GoogleSignIn(OAuthSignIn):
def __init__(self):
super(GoogleSignIn, self).__init__('google')
self.service = OAuth2Service(
name='google',
client_id=self.consumer_id,
client_secret=self.consumer_secret,
authorize_url='https://accounts.google.com/o/oauth2/auth',
access_token_url='https://accounts.google.com/o/oauth2/token',
base_url='https://www.googleapis.com/plus/v1/people/'
)
def authorize(self):
return redirect(self.service.get_authorize_url(
scope='email',
response_type='code',
redirect_uri=self.get_callback_url())
)
def callback(self):
if 'code' not in request.args:
return None, None, None
oauth_session = self.service.get_auth_session(
data={'code': request.args['code'],
'grant_type': 'authorization_code',
'redirect_uri': self.get_callback_url()},
decoder=json.loads
)
me = oauth_session.get('me').json()
me_email = None
for e in me['emails']:
if e['type'] == 'account':
me_email = e['value']
return (
me.get('id'),
me.get('displayName'),
me_email)
There is anothere way mentioned to use own decoder, but dont't know how to do that, please help me.
I figured out some dirty workaround using simplejson module (also got a loads method so nothing to change here)
importing that as
import simplejson as json
before Google class lets the authentification work as expected

Resources