I have the model object that looks like this
class CatalogModel(BaseModel):
#property
def custom_service(self):
return CustomService()
async def get_offers(self, catalog_name):
try:
svc_response = self.custom_service.get_offers(catalog_name=catalog_name)()
except BaseException:
raise SalesForceException()
return CustomOfferResponse().dump(svc_response)
I am trying to write a test for that get_offers function (that uses custom_services which is connecting to Salesforce)
My test is looking like this. I am using pytest, pytest vcr etc.
class TestCatalogModel:
catalog_name = 'CATALOG_1'
#freeze_time("2021-07-12")
async def test_get_offers(self, loop, offers, offers_response):
with MockUser(ident="test_model_get_offers"):
with patch(
"com.services.client.CustomService.get_offers", new=offers
):
eo_offers = await CatalogModel().get_offers(self.catalog_name)
assert offers_response == eo_offers
However when executing the test it fails with the error
E vcr.errors.CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('/test_api/recordings/2021-07-12/test_get_offers_model/salesforce/auth/client/services_oauth2_token.yaml') in your current record mode ('none').
E No match for the request (<Request (POST) https://server.salesforce.com/services/oauth2/token>) was found.
E No similar requests, that have not been played, found.
During handling of the above exception, another exception occurred:
...model.py:17: in test_get_offers
eo_offers = await CatalogModel().get_offers(self.catalog_name)
model.py:24: in get_offers
raise SalesForceException()
E ...SalesForceException: Error from Salesforce.
As far as I understand it is trying to connect to real Salesforce service, rather than using a mock. What is the problem?
I would avoid trying to partially patch methods on classes. Instead I would use inversion of control to allow mock instances to be used during tests. This avoids having to do any patching at all.
class CatalogModel(BaseModel):
def __init__(self, custom_service=None):
if custom_service is None:
custom_service = CustomService()
self.custom_service = custom_service
async def get_offers(self, catalog_name):
try:
svc_response = self.custom_service.get_offers(catalog_name=catalog_name)()
except BaseException:
raise SalesForceException()
return CustomOfferResponse().dump(svc_response)
Now writing the test becomes much simpler.
class TestCatalogModel:
catalog_name = 'CATALOG_1'
#freeze_time("2021-07-12")
async def test_get_offers(self, loop, offers, offers_response):
with MockUser(ident="test_model_get_offers"):
custom_service = mock.Mock()
custom_service.get_offers.return_value = offers
model = CatalogModel(custom_service)
eo_offers = await model.get_offers(self.catalog_name)
assert offers_response == eo_offers
Related
#pytest.fixture
def settings():
with open('../config.yaml') as yaml_stream:
return yaml.load(stream=yaml_stream)
#pytest.fixture
def viewers(settings):
try:
data = requests.get(settings['endpoints']['viewers']).json()
return data[0]['viewers']
except Exception:
print('ERROR retrieving viewers')
raise(SystemExit)
#pytest.fixture
def viewers_buffer_health(viewers):
print(viewers)
viewers_with_buffer_health = {}
for viewer in viewers:
try:
data = requests.get(settings['endpoints']['node_buffer_health']).replace('<NODE_ID>', viewer)
except Exception as e:
print('ERROR retrieving buffer_health for {}'.format(viewer))
raise(SystemExit)
viewers_with_buffer_health[viewer] = data[0]['avg_buffer_health']
return viewers_with_buffer_health
The fixture viewers_buffer_health is failing all the time on the requests because 'function' object is not subscriptable
Other times I have seen such error it has been because I was calling a variable and a function by the same name, but it's not the case (or I'm blind at all).
Although it shouldn't matter, the output of viewers is a list like ['a4a6b1c0-e98a-42c8-abe9-f4289360c220', '152bff1c-e82e-49e1-92b6-f652c58d3145', '55a06a01-9956-4d7c-bfd0-5a2e6a27b62b']
Since viewers_buffer_health() doesn't have a local definition for settings it is using the function defined previously. If it is meant to work in the same manner as viewers() then you will need to add a settings argument to its current set of arguments.
settings is a function.
data = requests.get(settings()['endpoints']['node_buffer_health']).replace('<NODE_ID>', viewer)
I'm trying to unit test a function that I run threaded within a view. Whenever I try to mock it, it always goes to the original function, no the mocked function.
The code I'm testing, from the view module:
def restart_process(request):
batch_name = request.POST.get("batch_name", "")
if batch_name:
try:
batch = models.Batch.objects.get(num=batch_name)
except models.Batch.DoesNotExist:
logger.warning("Trying to restart a batch that does not exist: " + batch_name)
return HttpResponse(404)
else:
logger.info(batch_name + " restarted")
try:
t = threading.Thread(target=restart_from_last_completed_state, args=(batch,))
t.daemon = True
t.start()
except RuntimeError:
return HttpResponse(500, "Threading error")
return HttpResponse(200)
else:
return HttpResponse(400)
The test function:
class ThreadTestCases(TransactionTestCase):
def test_restart_process(self):
client = Client()
mock_restart_from_last_completed_state = mock.Mock()
with mock.patch("processapp.views.restart_from_last_completed_state", mock_restart_from_last_completed_state):
response = client.post('/batch/restart/', {"batch_name": "BATCH555"})
self.assertEqual(response.status_code, 200)
mock_restart_from_last_completed_state.assert_called_once()
The URL:
url(r'^batch/restart/$', views.restart_from_last_completed_state, name="restart_batch"),
I always get this error:
ValueError: The view processapp.processing.process_runner.restart_from_last_completed_state didn't return an HttpResponse object. It returned None instead.
I put a print command in the original function (restart_from_last_completed_state) and it always runs so the mocking does not take place.
The error seems to take the function as a view although it is not.
I'm not sure where the error is, the threading, testing, something else?
The URL variable was wrong. Was supposed to be views.restart_process not views.restart_from_last_completed_state
A copy/paste error as so many times...
I'm writing a web-application based on the webapp2 framework. I'm using a common base exception for all the errors I'm explicitly throwing, like
class MyBaseException(Exception):
def __init__(self, status, code, message):
self.status_code = status
self.error_code = code
self.error_message = message
and have a BaseRequestHandler that outputs a simple JSON response for errors, defaulting to a 500/Generic error for unexpected exceptions.
class BaseHandler(webapp2.RequestHandler):
def handle_exception(self, e, debug):
logger.exception(e)
status = e.status_code if isinstance(e, MyBaseException) else 500
code = e.error_code if isinstance(e, MyBaseException) else 'GENERIC_ERROR'
message = e.error_message if isinstance(e, MyBaseException) else 'Blah blah blah'
self.response.set_status(status)
self.response.content_type = 'application/json'
self.response.write(json.encode({"code": code, "message": message})
Those isinstance checks look ugly to me, so I'm thinking there has to be a better way. Suggestions?
EDIT What has this to do with downcasting?
In java, my "native" language, I'd do something like
MyBaseException b = (MyBaseException) e;
JSONObject j = new JSONObject();
j.put("error_code", e.getCode());
j.put("error_message", e.getErrorMessage());
...
but python has no explicit type casts so... is it possible to do something like that?
The usual Python idiom is to ignore the class of the object altogether, so you'd just do:
status = e.status_code
code = e.error_code
message = e.error_message
This is referred to as duck typing.
If you may need to handle other types of exceptions (where, in your Java example you'd get a ClassCastException) the standard Python idiom is to just wrap the whole thing in a try...catch:
try:
status = e.status_code
etc.
catch AttributeError: # i.e. e doesn't have one of the listed attributes
status = "500"
etc.
I'm having a problem understanding how to serve my data best. I have 2 models, one is record and the other is log, they have a 1 to many relationship respectively. I'd like to serve this using tg's RestController so I can do mysite.com/api/record_id/log
So far I have this:
class API(RestController):
#expose('json')
def get_all(self):
records = DB.query(Record).all()
return dict(records=records)
#expose('json')
def get_one(self, record_id):
try:
record = DB.query(Record).filter(
Record.record_id==record_id).one()
except NoResultFound:
abort(404)
return dict(record=record)
#expose('json')
def log(self, record_id):
try:
log = DB.query(Log).filter(
Log.record_id==record_id).all()
except NoResultFound:
abort(404)
return dict(log=log)
This works, however, if I go to mysite.com/api/log then it maps (as expected) to the log method and complains about the missing variable record_id. How can this be done so the log method is only accessible after the record resource?
I've create little class for parsing websites.
There's URLError exception:
def visit(self, url, referer=None, data=None):
(...)
# Return BeautifulSoup instance.
try:
return BeautifulSoup(self.opener.open(self.request))
# URLError.
except urllib.error.URLError as error:
return error
Everything works okay. But I'm in need to create a wrapper of this function.
def get_links(self, *args, **kwargs):
# Get links with BeautifulSoup.
self.links = self.visit(*args, **kwargs).find_all('a')
Get_links function also works well until there is URLError (403, 404, whatever...). How can I solve this problem? Is there something as inheritance exceptions?
Your visit() function catches exception and returns you a URLError object, on which you're calling find_all(), which it doesn't have.
Something in lines of:
self.links = self.visit(*args, **kwargs)
if not isinstance(self.links, urllib.error.URLError):
self.links = self.links.find_all('a')
else:
# Do something with an HTTP Error
Should give you an idea of a flow. You can't catch that exception in your outer get_links() because it's already caught by visit() and is simply returned.
If you want to catch it in get_links(), change
return error
to
raise error
in your visit() method, although then you'll be throwing the exception you just caught again, I'm not sure whether this is the behavior you want.