I running some unit tests for a method with mock objects. In the method, attributes are set, but I can't seem to access them in the unit test. When I try I get back a mock object, not the string I am trying to access
Here is my unit test
#mock.patch("bpy.data.cameras.new")
def test_load_camera(self, mock_camera_data):
loader = self.SceneLoader(self.json_data)
self.mock_bpy.context.scene.objects.link.return_value = 5
cam_data = {"name": "camera 1",
"type": "PERSP",
"lens_length": 50.0,
"lens_unit": "MILLIMETERS",
"translation": [
4.5,
74,
67
],
"rotation": [
-0.008,
-0.002,
0.397,
0.918
]
}
data = mock.Mock()
mock_camera_data.return_value = data
loader._load_camera(cam_data)
assert mock_camera_data.called_with("Camera")
assert data.type == "PERSP"
The method I am testing is
def _load_camera(self, cam_data):
camera_data = bpy.data.cameras.new("Camera")
camera_data.type = cam_data["type"]
When I run the unit test, I get this error
AssertionError: assert <Mock name='new().type' id='140691645666360'> == 'PERSP'
E + where <Mock name='new().type' id='140691645666360'> = <Mock name='new()' id='140691645594760'>.type```
Figured it out. I needed to do configure mock so the code now looks like
data = mock.Mock()
data.configure_mock(type=None)
mock_camera_data.return_value = data
loader._load_camera(cam_data)
You need to configure/set the attribute first in the mock so that you can access it later. Now the attribute "type" can be accessed after the method has been run
Related
Using pytest, I wish to mock a function that can raise several exceptions. My app will catch the exception and create a response object, and I need to assert that each response contains the correct message, type, and in reality several other properties.
In the first instance, I have created a separate fixture to mock the function and raise each exception, and then I'm passing those fixtures in to a test with a series of events. However, because my fixtures are mocking the same function, the exception raised for every test is the same - in this case, that would be mock_exc_2, the last fixture passed to the test.
One thing I know will work is to separate the test function into multiple functions, but that seems inefficient because any future change would need to be made to multiple functions.
What is the most appropriate / efficient way to do this with with pytest?
Fixtures in 'conftest.py'
#pytest.fixture(scope='function')
def mock_exc_1(mocker):
def mock_response(self, path):
raise MissingOrgIdException()
mocker.patch('proxy.app.mcpcore.ProxyRequest._validate_org_id', mock_response)
#pytest.fixture(scope='function')
def mock_exc_2(mocker):
def mock_response(self, path):
# Parameter values are not emitted in the error message that is included in the response to the user.
raise InvalidOrgIdException('xxx', 'xxx')
mocker.patch('proxy.app.mcpcore.ProxyRequest._validate_org_id', mock_response)
# Working fixtures for 'event and 'mock_context' go here.
Broken tests in 'test_events.py'
In this scenario, only the last test is successful because both mock_exc_1 and mock_exc_2 are mocking the same function.
bad_request_args = ('event, expected',
[
(
'400-org-id-missing.json',
{
'message': 'URI path does not include an organisation ID.',
'type': 'MissingOrgIdException'
}
),
(
'400-org-id-invalid.json',
{
'message': 'Invalid organisation ID in URI path.',
'type': 'InvalidOrgIdException'
}
)
]
)
#pytest.mark.parametrize(*bad_request_args, indirect=['event'])
def test_400_events(event, expected, mock_context, mock_exc_1, mock_exc_2):
response = lambda_handler(json.loads(event), mock_context)
body = json.loads(response['body'])
assert body['errorMessage'] == expected['message']
assert body['errorType'] == expected['type']
Working tests in 'test_events.py'
Here the tests will pass, because each test is only using the fixture that raises the correct exception for the mocked function.
However, in reality there are more than two exceptions to test, and having to maintain a parameter with the parametrize args and a function to test each exception seems inefficient and prone to error when a change is made.
bad_request_args_1 = ('event, expected',
[
(
'400-org-id-missing.json',
{
'message': 'URI path does not include an organisation ID.',
'type': 'MissingOrgIdException'
}
)
]
)
bad_request_args_2 = ('event, expected',
[
(
'400-org-id-invalid.json',
{
'message': 'Invalid organisation ID in URI path.',
'type': 'InvalidOrgIdException'
}
)
]
)
#pytest.mark.parametrize(*bad_request_args_1, indirect=['event'])
def test_400_events_1(event, expected, mock_context, mock_exc_1):
response = lambda_handler(json.loads(event), mock_context)
body = json.loads(response['body'])
assert body['errorMessage'] == expected['message']
assert body['errorType'] == expected['type']
#pytest.mark.parametrize(*bad_request_args_2, indirect=['event'])
def test_400_events_2(event, expected, mock_context, mock_exc_2):
response = lambda_handler(json.loads(event), mock_context)
body = json.loads(response['body'])
assert body['errorMessage'] == expected['message']
assert body['errorType'] == expected['type']
It seems that at the moment there is no "proper" way to do this. However, it is possible to do this by using the request.getfixturevalue('fixture')
bad_request_args = ('event, expected, mock_fixture_name',
[
(
'400-org-id-missing.json',
{
'message': 'URI path does not include an organisation ID.',
'type': 'MissingOrgIdException'
},
'mock_exc_1'
),
(
'400-org-id-invalid.json',
{
'message': 'Invalid organisation ID in URI path.',
'type': 'InvalidOrgIdException'
},
'mock_exc_2'
)
]
)
#pytest.mark.parametrize(*bad_request_args, indirect=['event'])
def test_400_events(event, expected, mock_fixture_name, mock_context, request):
request.getfixturevalue(mock_fixture_name)
response = lambda_handler(json.loads(event), mock_context)
body = json.loads(response['body'])
assert body['errorMessage'] == expected['message']
assert body['errorType'] == expected['type']
I am trying to patch out the Azure Digital Twin API in my code. Currently I have achieved a way which works but is probably not the most Pythonic by nesting with patch statements.
What is the best way to rewrite this such that I can use it in multiple test functions and change the return values if needed?
def test_create_digital_twin(self):
with patch("endpoints.digital_twin.ClientSecretCredential"):
with patch("azure_digital_twin.create_digital_twin.DigitalTwinsClient.query_twins",) as mock_query:
with patch("azure_digital_twin.create_digital_twin.DigitalTwinsClient.upsert_digital_twin") as mock_upsert_twin:
with patch("azure_digital_twin.create_digital_twin.DigitalTwinsClient.upsert_relationship") as mock_upsert_relationship:
mock_query.return_value = []
mock_upsert_twin.return_value = {
"$dtId":"spc-1",
"$etag":"random",
"$model":"dtmi:digitaltwins:rec_3_3:core:Asset;1"
}
mock_upsert_relationship.return_value = {
"$relationshipId":"spc-1-hasPart-spc-2",
"$sourceId":"spc-1",
"$targetId" : "spc-2",
"$relationshipName":"hasPart"
}
response = self.client.post(
endpoint,
params={"node" : "spc-1"},
)
assert response.status_code == status.HTTP_201_CREATED
You might use an ExitStack from the contextlib module.
from contextlib import ExitStack
def test_create_digital_twin(self):
with ExitStack() as es:
def make_azure_patch(x):
return es.enter_context(patch(f'azure_digital_twin.create_digital_twin.DigitalTwinsClient.{x}'))
es.enter_context("endpoints.digital_twin.ClientSecretCredential"))
mock_query = make_patch("query_twins")
mock_upsert_twin = make_patch("upsert_digital_twin")
mock_upsert_relationship = make_patch("upsert_relationship")
mock_query.return_value = []
mock_upsert_twin.return_value = {
"$dtId":"spc-1",
"$etag":"random",
"$model":"dtmi:digitaltwins:rec_3_3:core:Asset;1"
}
mock_upsert_relationship.return_value = {
"$relationshipId":"spc-1-hasPart-spc-2",
"$sourceId":"spc-1",
"$targetId" : "spc-2",
"$relationshipName":"hasPart"
}
response = self.client.post(
endpoint,
params={"node" : "spc-1"},
)
assert response.status_code == status.HTTP_201_CREATED
make_azure_patch is just a helper function to reduce the length of the lines creating three of the individual patches.
So I am writing a trading bot in python. It is more for fun and I just started. Every method works alone, so I excluded them here, to not give you 300lines of Code. I also exclude the hole analyze method, since even if I clear the rest of the method, the same Error appears. When I use analyze just once, it doesn't do anything but also gets no error and when I use it in a loop I get an error:
Exception has occurred: KeyError
'result' (mark get_crypto_data)
This doesn't make sense to me, since if I print get_crypto_data it works just fine.
def get_crypto_data(pair,since):
return api.query_public("OHLC", data = {"pair" : pair, "since" : since})["result"][pair] #array of prices (60sek)
def analyze(pair,since):
data = get_crypto_data(pair[0]+pair[1], since)
if __name__ == "__main__":
api = krakenex.API()
api.load_key("KrakenKey.txt")
pair = ('XETH' , 'ZEUR') # Currency pair
since = str(int(time.time() - 3600))
while True:
analyze(pair,since)
The data structure receiving from the API looks like this(example)(without indents):
{
"error": [ ],
"result": {
"XXBTZUSD": [
[
1616662740,
"52591.9",
"52599.9",
"52591.8",
"52599.9",
"52599.1",
"0.11091626",
5
],
[
1616662800,
"52600.0",
"52674.9",
"52599.9",
"52665.2",
"52643.3",
"2.49035996",
30
],
[
1616662860,
"52677.7",
"52686.4",
"52602.1",
"52609.5",
"52634.5",
"1.25810675",
20
],
[
1616662920,
"52603.9",
"52627.5",
"52601.2",
"52616.4",
"52614.0",
"3.42391799",
23
],
[
1616662980,
"52601.2",
"52601.2",
"52599.9",
"52599.9",
"52599.9",
"0.43748934",
7
]
],
"last": 1616662920
}
}
Context
A KeyError in Python is raised when you try to search for an item in an object that doesn't exist. For example, if you make a request:
response = requests.get(url).json()
response['nonexistent']
# KeyError raised as nonexistent doesn't exist in the object
With that in mind, a KeyError when you make an API call to receive this object:
api.query_public("OHLC", data = {"pair" : pair, "since" : since})
We can infer that for whatever reason, ["result"] is not a key in the object above. To debug the issue, follow the steps below.
Debugging
Make the API call and save the response to a variable. Then print the variable along with its type to understand how you can interact with it.
response = api.query_public("OHLC", data = {"pair" : pair, "since" : since})
print(response, type(response))
If it's in a String format (or another standard convertible format), you can use the inbuilt json library to convert it to a dictionary object you can call as you did in your example.
import json
response = api.query_public("OHLC", data = {"pair" : pair, "since" : since})
response = json.loads(response)
Otherwise, given the structure of the output you displayed, it may be wise to convert the response to a string and then follow step 2.
import json
# Get the response
response = api.query_public("OHLC", data = {"pair" : pair, "since" : since})
# Convert to string
response = str(response)
# Convert to dictionary using JSON
response = json.loads(response)
# Call the data you want
data = response["result"]["XXBTZUSD"]
I have this config.py file:
# config.py
maria = dict(
corners = [1,2,3,4],
area = 2100
)
john = dict(
corners = [5,6,7,8],
area = 2400
)
and want to use parameters from it by running my main program using argsparse. somewhat like this:
# main.py
import config
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("user", help="maria or john")
args = parser.parse_args()
print(args.user)
print(config.args.user['corners'])
when I run:
pyhton3 main.py maria
I get syntax error on the 2nd print, where I would like to get [1,2,3,4].
How can I use the argument from argparse as an attribute to access the appropriate data in the config file?
IIUC:
You can use the getattr built in function in python.
The getattr(object, name[, default]):
Return the value of the named attribute of object. name must be a
string. If the string is the name of one of the object’s attributes,
the result is the value of that attribute. For example, getattr(x,
'foobar') is equivalent to x.foobar. If the named attribute does not >exist, default is returned if provided, otherwise AttributeError is raised.
Replace:
print (config.args.user['corners'])
With:
print(getattr(config, args.user)["corners"])
Avoid using executable Python code for configuration. Use something like JSON:
config.json would look like
{
"maria": {
"corners": [1,2,3,4],
"area": 2100
},
"john": {
"corners": [5,6,7,8],
"area": 2400
}
}
And your script would use
# main.py
import json
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("user", help="maria or john")
args = parser.parse_args()
with open("config.json") as f:
config = json.load(f)
print(args.user)
print(config[args.user]['corners'])
One way to go around this is to wrap your parameters in one general dict:
# config.py
params = {'maria': {'corners': [1,2,3,4], 'area': 2100},
'john': {'corners': [5,6,7,8], 'area': 2400}}
And then you can simply do in main.py:
print(config.params[args.user]['corners'])
I'm writing a set of tools to test the behavior of a custom HTTP server: whether it is setting appropriate response codes, header fields etc. I'm using pytest to write tests.
The goal is to make requests to several resources, and then evaluate the response in multiple tests: each test should test a single aspect of the HTTP response. However, not every response is tested with every test and vice-versa.
To avoid sending the same HTTP request multiple time and reuse HTTP responses messages, I'm thinking of using pytest's fixtures, and to run the same tests on different HTTP responses I'd like to use pytest's generate test capabilities.
import pytest
import requests
def pytest_generate_tests(metafunc):
funcarglist = metafunc.cls.params[metafunc.function.__name__]
argnames = sorted(funcarglist[0])
metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
for funcargs in funcarglist])
class TestHTTP(object):
#pytest.fixture(scope="class")
def get_root(self, request):
return requests.get("http://test.com")
#pytest.fixture(scope="class")
def get_missing(self, request):
return requests.get("http://test.com/not-there")
def test_status_code(self, response, code):
assert response.status_code == code
def test_header_value(self, response, field, value):
assert response.headers[field] == value
params = {
'test_status_code': [dict(response=get_root, code=200),
dict(response=get_missing, code=404), ],
'test_header_value': [dict(response=get_root, field="content-type", value="text/html"),
dict(response=get_missing, field="content-type", value="text/html"), ],
}
The problem appears to be in defining params: dict(response=get_root, code=200) and similar definitions do not realize, I'd like to bind on the fixture and on on the actual function reference.
When running tests, I get this kinds of errors:
________________________________________________ TestHTTP.test_header_value[content-type-response0-text/html] _________________________________________________
self = <ev-question.TestHTTP object at 0x7fec8ce33d30>, response = <function TestHTTP.get_root at 0x7fec8ce8aa60>, field = 'content-type', value = 'text/html'
def test_header_value(self, response, field, value):
> assert response.headers[field] == value
E AttributeError: 'function' object has no attribute 'headers'
test_server.py:32: AttributeError
How may I convince the pytest to take the fixture value instead of the function?
No need to generate tests from fixtues, just parameterize your fixture and write regular tests for the values it returns:
import pytest
import requests
should_work = [
{
"url": "http://test.com",
"code": 200,
"fields": {"content-type": "text/html"}
},
]
should_fail = [
{
"url": "http://test.com/not-there",
"code": 404,
"fields": {"content-type": "text/html"}
},
]
should_all = should_work + should_fail
def response(request):
retval = dict(request.param) # {"url": ..., "code": ... }
retval['response'] = requests.get(request.param['url'])
return retval # {"reponse": ..., "url": ..., "code": ... }
# One fixture for working requests
response_work = pytest.fixture(scope="module", params=should_work)(response)
# One fixture for failing requests
response_fail = pytest.fixture(scope="module", params=should_fail)(response)
# One fixture for all requests
response_all = pytest.fixture(scope="module", params=should_all)(response)
# This test only requests failing fixture data
def test_status_code(response_fail):
assert response_fail['response'].status_code == response_fail['code']
# This test all requests fixture data
#pytest.mark.parametrize("field", ["content-type"])
def test_header_content_type(response_all, field):
assert response_all['response'].headers[field] == response_all['fields'][field]