I'm trying to create a Mock of a library's (Hammock) call to a POST method that has an attribute status_code. Here is my test code:
def test_send_text(self):
Hammock.POST = Mock(status_code=201)
print Hammock.POST.status_code
self.task.payload = fixtures.text_payload
self.task.send_text()
# ········
Hammock.POST.assert_any_call()
When I print Hammock.POST.status_code, I get what I expect -- 201. However, when we move into the code I'm testing:
response = self.twilio_api('Messages.json').POST(data=self.payload)
print '*' * 10
print response
print response.status_code
if response.status_code == 201:
self.logger.info('Text message successfully sent.')
else:
raise NotificationDispatchError('Twilio request failed. {}. {}'.format(response.status_code,
response.content))
Things get weird. response is, indeed, a Mock object. But response.status_code, instead of being 201 like when I try it in the test, is an object: <Mock name='mock().status_code' id='4374061968'>. Why is my mocked attribute working in the test code and not in the code that I'm testing?
The issue is POST().status_code vs POST.status_code. POST.status_code will indeed == 201, but the return object from POST() is a completely new mock object.
What you are looking for is roughly
Hammock.POST = Mock()
Hammock.POST.return_value = Mock(status_code=201)
Related
I have http get method mocked so to get the response from the url without actually sending the url:
def get(url, retries=None, back_off_factor=None, max_back_off=None, timeout=None, response_encoding=None,
retry_on_timeout=None, retry_codes=None, **kwargs):
return _make_request("GET", url,
retries=retries, back_off_factor=back_off_factor,
max_back_off=max_back_off,
timeout=timeout,
response_encoding=response_encoding,
retry_on_timeout=retry_on_timeout,
retry_codes=retry_codes,
**kwargs)
#patch('lib.httputil.get')
def test_harvest(self, mock_get):
articles = json.load(json_file)
# Configure the mock to return a response with an OK status code. Also, the mock should have
# a `json()` method that returns a list of todos.
mock_get.return_value = Mock(ok=True)
mock_get.return_value.json.return_value = articles
mock_get.return_value.status_code = 200
the_rest_of_the_test()
But I realized I need to mock it only if the URL is specific. I know I could use new keyword and do:
def mock_get(self, url):
if url == MY_SPECIFIC_URL:
{...}
else:
self.old_get(url)
{...}
with mock.patch('portality.lib.httputil.get', new=self.mock_get):
the_rest_of_the_test()
but I don't really know how to mock the Response object so that it returns the correct status code and gives the correct result to .json() method.
How can I use both of these approaches altogether so that on one hand I can use the conditional but on the other mock the Response in easy way?
I suggest that you use the requests library, along with responses which is specifically meant for returning the desired HTTP responses.
You can mock specific urls:
import responses
import requests
#responses.activate
def test_simple():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
json={'error': 'not found'}, status=404)
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.json() == {"error": "not found"}
assert len(responses.calls) == 1
assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
assert responses.calls[0].response.text == '{"error": "not found"}'
And you can exclude other urls:
responses.add_passthru(re.compile('https://percy.io/\\w+'))
I want to test exceptions with Pytest. I send an HTTP request and get a response. I want the response to be corrupt so response.json() goes to the except block. Below are the examples.
Send a request, receive a response:
def send_message_json():
# ...
try:
response = cls.send_message(method, url, **kwargs)
if response:
return response.json() # this is what should fail
except simplejson.errors.JSONDecodeError as err:
raise err # this is to be achieved
The unit test should assert that the simplejson.errors.JSONDecodeError should be raised.
#mock.patch.object(Service, 'send_message_json')
def test_send_message_json_exception(mock_send):
svc = Service()
with pytest.raises(simplejson.errors.JSONDecodeError): # this should assert the exceptions was raised
svc.send_message_json("GET", 'http://my.url/')
I fail to activate the exception raise by the pytest.mock.object. What would make .json() fail in the mock?
I figured it out by mocking the response of the actual request.
In the original question there's send_message_json() method.
It calls send_message() method that returns requests.Response. So I mocked the return value of send_message() that is also a mock instead of send_message_json():
#mock.patch.object(Service, 'send_message') # mock send_message instead
def test_send_message_json_exception(mock_send):
svc = Service()
mocked_response = Response()
mocked_response.status_code = 200
mocked_response.data = '<!doctype html>' # so this is obviously not a JSON
mock_send.return_value = mocked_response # apply the mocked response here
with pytest.raises(simplejson.errors.JSONDecodeError):
svc.send_message_json("GET", 'http://my.url/') # this calls send_message() and returns a bad mocked requests.Response
The bad mocked requests.Response fails at mocked_response.json() in send_message_json() and raises simplejson.errors.JSONDecodeError
In the pytest documentation it says that you can customize the output message when an assert fails. I want to customize the assert message when testing a REST API method it returns an invalid status code:
def test_api_call(self, client):
response = client.get(reverse('api:my_api_call'))
assert response.status_code == 200
So I tried to put a piece of code like this in conftest.py
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, rest_framework.response.Response):
return left.json()
But the problem is left is the actual value of response.status_code so it's an int instead of a Response. However the default output messages throws something like:
E assert 400 == 201 E + where 400 = .status_code
Saying that the error 400 comes from an attribute status_code of a object Response.
My point is that there is a kind of introspection of the variable being evaluated. So, how can I customize the assert error message in a comfortable way to get a similar output to example posted above?
you could use Python built-in capability to show custom exception message:
assert response.status_code == 200, f"My custom msg: actual status code {response.status_code}"
Or you can built a helper assert functions:
def assert_status(response, status=200): # you can assert other status codes too
assert response.status_code == status, \
f"Expected {status}. Actual status {response.status_code}. Response text {response.text}"
# here is how you'd use it
def test_api_call(self, client):
response = client.get(reverse('api:my_api_call'))
assert_status(response)
also checkout: https://wiki.python.org/moin/UsingAssertionsEffectively
I've got a function like:
def request_API(request_url): #To test
fail_request = -1
response = requests.get(request_url)
if response.ok:
infos = json.loads(response.text)
if infos.has_key("movie"):
return infos["movie"]
if infos.has_key("tvseries"):
return infos["tvseries"]
print "Allocine API Request Error"
return fail_request
I did a test like:
def test_should_fail_to_request(self):
#GIVEN
response = json.dumps({"error":{"code":0,"$":"No result"}})
requests.get = mock.MagicMock(return_value=response)
#WHEN
response = my_mod.request_allocine_API("") #requests mocked so we don't need an URL
#THEN
self.assertEqual(response, -1, "There should be an error in the API")
But I've got an error:
AttributeError: 'str' object has no attribute 'ok'
I know it come from the fact that when I mock request.get I return a JSON. My question is what is the proper way to do it. Have I to recreate an object requests or is there more simple way to do so.
You are mocking requests.get, which normally returns an Response object, to instead return a plain string. Try having it return an Response object instead:
from mock import patch
from requests import Response
def test_should_fail_to_request(self):
mock_response = Response()
mock_response.status_code = 404
mock_response.body = json.dumps({"error":{"code":0,"$":"No result"}})
with patch('requests.get', return_value=mock_response):
response = my_mod.request_allocine_API("")
self.assertEqual(response, -1, "There should be an error in the API")
I use requests-mock library which works well.
the document is in : https://requests-mock.readthedocs.org/en/latest/
The best feature is supporting for regex.
So, I've just started using mock with a Django project. I'm trying to mock out part of a view which makes a request to a remote API to confirm a subscription request was genuine (a form of verification as per the spec I'm working to).
What I have resembles:
class SubscriptionView(View):
def post(self, request, **kwargs):
remote_url = request.POST.get('remote_url')
if remote_url:
response = requests.get(remote_url, params={'verify': 'hello'})
if response.status_code != 200:
return HttpResponse('Verification of request failed')
What I now want to do is to use mock to mock out the requests.get call to change the response, but I can't work out how to do this for the patch decorator. I'd thought you do something like:
#patch(requests.get)
def test_response_verify(self):
# make a call to the view using self.app.post (WebTest),
# requests.get makes a suitable fake response from the mock object
How do I achieve this?
You're almost there. You're just calling it slightly incorrectly.
from mock import call, patch
#patch('my_app.views.requests')
def test_response_verify(self, mock_requests):
# We setup the mock, this may look like magic but it works, return_value is
# a special attribute on a mock, it is what is returned when it is called
# So this is saying we want the return value of requests.get to have an
# status code attribute of 200
mock_requests.get.return_value.status_code = 200
# Here we make the call to the view
response = SubscriptionView().post(request, {'remote_url': 'some_url'})
self.assertEqual(
mock_requests.get.call_args_list,
[call('some_url', params={'verify': 'hello'})]
)
You can also test that the response is the correct type and has the right content.
It's all in the documentation:
patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)
target should be a string in the form ‘package.module.ClassName’.
from mock import patch
# or #patch('requests.get')
#patch.object(requests, 'get')
def test_response_verify(self):
# make a call to the view using self.app.post (WebTest),
# requests.get makes a suitable fake response from the mock object