I have a unit test and mocking an external call. Here's the abbreviated code for the function I'm trying to test in service.py file
def post_data()
req = request.Request()
response = req.post(payload, url, json.dumps({"data": kwargs['data']}))
if response['request']['status'] == 'SUCCESS' and response['data']:
run_id = response.json()['data']['run_id']
response = track_run_to_completion(run_id, **kwargs)
return response
Here's my unit test method
#patch('service.request.Request.post')
def test_post_data(self, mock_post):
kwargs = {'a':'abc'}
expected = json.dumps({'request':{'status':'ERROR'},'data':{}})
mock_post.return_value = MagicMock(status_code=200, response=expected)
mock_post.assert_called_once_with({'action': 'trigger'}, 'a/abc', '{"data": {}}') # SUCCESS!
result = service.post_data(**kwargs)
print result
When I print the result, I was expecting to see the json, but get <MagicMock name='post()' id='4488707600'>. What am I missing here? I'm new to Python and started writing unit tests for an existing application.
Related
I have a function that I am trying to test in querySomething.py:
class QuerySomething:
def retrieveIssues(self,token):
responses = []
if "customFields" in self._event:
if not self.custom_fields:
fields = []
else:
fields = self.custom_fields
else:
fields = []
for issueTypeKey, issueTypeValue in self.issueTypes.items():
print(issueTypeKey, ":", issueTypeValue)
query = self.getQuery(issueTypeValue, self.status, fields)
respons = httpClient.get_request(query, token)
responses.append(respons)
return responses
And the test file:
def mock_getQuery():
return "QUERY"
def mock_response(state):
if state=="unauth":
with open("src/tests/mockdata/unauthorized_api_response.json","r") as response_file:
unauth_error = response_file.read()
return json.dumps(unauth_error)
elif state=="auth":
with open("src/tests/mockdata/success_api_response.json","r") as response_file:
success_message = response_file.read()
return json.dumps(success_message)
return "No message"
class test_query(unittest.TestCase):
#mock.patch("querySomething.QuerySomething.getQuery", side_effect=mock_getQuery)
#mock.patch("httpClient.get_request", side_effect=mock_response)
def test_retreiveIssues_unauth_response(self,mock_get,QuerySomething):
self.assertEqual(QuerySomething.retrieveIssues("token"),mock_response("unauth"))
if __name__ == "__main__":
unittest.main()
I am trying to mock the httpClient.get_request so that it gets the JSON file instead of reaching out to the API. We want to test an unauthorized response and a success response which explains the mock_response function. However, when I run the test, I get the following:
AssertionError: <MagicMock name='getQuery.retri[36 chars]712'> != '"{\\n \\"errorMessages\\": [\\n [131 chars]\n}"'
which is somewhat correct, but we need just the text, not the object. I read that I need to call the function, but when I try to call the function it throws a ModuleNotFound or NotAPackage error. What do I need to do to mock the httpClient.get_request and return the JSON string in the retrieveIssues function?
Updated, I was able to pull the JSON from the other file, and then was able to mock the return value as follows:
QuerySomething.retrieveIssues.return_value=load_json("unauth")
where load_json("unauth") pulls from the JSON response file.
I'm using a 3rd party library "mailjet" to send email.
Here's the doc: https://dev.mailjet.com/email/guides/getting-started/#prerequisites
This is the method in send_email.py that I want to test:
from mailjet_rest import Client
def send_email(email_content):
client = Client(auth=(api_key, api_secret))
response = client.send.create(data=email_content)
if response.status_code != 200:
// raise the error
else:
print(response.status_code)
return response.status_code
...
I wanted to mock the status code. This is what I tried in test_send_email.py:
from unittest.mock import MagicMock, patch
import unittest
import send_email
class TestClass(unittest.TestCase):
#patch("send_email.Client")
def test_send_email(self, _mock):
email_content = {...}
_mock.return_value.status_code = 200
response = send_email(email_content)
// assert response == 200
When I run the test, it didn't print out the status code, it prints:
<MagicMock name='Client().send.create().status_code' id='140640470579328'>
How can I mock the status_code in the test? Thanks in advance!!!!!
You are putting the attribute in the wrong place.
You mock the Client, which is correct, and put something in its return_value, which is also good.
However, the line
response = client.send.create(data=email_content)
applies on the return_value of your mock (client), the send attribute and its create method.
So what you need is to set the return_value.status_code of this create method.
class TestClass(unittest.TestCase):
#patch("send_email.Client")
def test_send_email(self, mock_client):
email_content = {...}
# this puts 200 in Client().status_code
# mock_client.return_value.status_code = 200
# that puts 200 in Client().send.create().status_code
mock_client.return_value.send.create.return_value.status_code = 20
response = send_email(email_content)
assert response == 200
I have a Scrapy XMLFeedSpider and I'm trying to test the following parse_node function:
def parse_node(self, response, selector):
date = selector.xpath('pubDate/text()').extract_first()
url = selector.xpath('link/text()').extract_first()
if date < self.cutoff_date: # TEST VALIDITY OF THE DATE
print "Invalid date"
self.log("Article %s before crawler start date" % url)
else:
print "Valid date"
yield scrapy.Request(url, self.parse_post)
I'm trying to test the function for both a valid and an invalid date:
#mock.patch('my_spiders.spiders.myspider.scrapy.Request')
def test_parse_node(self, scrapy_request):
scrapy_request.return_value = mock.MagicMock()
self.spider.log = mock.MagicMock()
mock_response = mock.MagicMock()
mock_selector = mock.MagicMock()
date = self.spider.start_date.strftime("%c")
url = "https://google.com"
mock_selector.xpath.return_value.extract_first = mock.MagicMock(
side_effect=[date, url]
)
parsed_node = self.spider.parse_node(mock_response, mock_selector)
self.assertEqual(tuple(parsed_node)[0], scrapy_request.return_value)
self.spider.log.assert_not_called()
scrapy_request.assert_called_once_with(url, self.spider.parse_post)
#mock.patch('my_spiders.spiders.myspider.scrapy.Request')
def test_parse_node_invalid_date(self, scrapy_request):
scrapy_request.return_value = mock.MagicMock()
self.spider.log = mock.MagicMock()
mock_response = mock.MagicMock()
mock_selector = mock.MagicMock()
date_object = self.spider.start_date - datetime.timedelta(days=1)
date = date_object.strftime("%c")
url = "https://google.com"
mock_selector.xpath.return_value.extract_first = mock.MagicMock(
side_effect=[date, url]
)
parsed_node = self.spider.parse_node(mock_response, mock_selector)
# TODO: figure out why this doesn't work
# self.spider.log.assert_called_once()
scrapy_request.assert_not_called()
The first test, test_parse_node runs as expected. The problem is with the test_parse_node_invalid_date function. If I put a debugger in the parse_node function it doesn't get called. The print functions don't get called either.
I suspect this is some kind of issue with the yield statement/generator, but can't figure out what's happening. Why isn't the second test running through the parse_node function as I'd expect it would?
A python generator function simply returns an iterator. To actually debug that iterator, I had to start the iteration process by invoking the next() method:
parsed_node = self.spider.parse_node(mock_response, mock_selector).next()
I also had to make sure that each test instantiated a new generator, because a generator can only be iterated over one time.
Then I could step through and debug/complete my test as necessary.
Does pytest (2.8.3) have an equivalent of self.subTest() (as found in Python 3's unittest)?
Here's a simplified version of the code I'm trying to convert:
class MyUnittestTest(TestCase):
def test_permutations(self):
on_off = True, False
for prefix, params, suffix in product(on_off, on_off, on_off):
expected_prefix = 'something' if prefix else ''
expected_params = ('something',) if params else ()
expected_suffix = b'something' if suffix else b''
with self.subTest(prefix=prefix, params=params, suffix=suffix):
result = do_magic(prefix=prefix, params=params, suffix=suffix)
self.assertEqual(result.prefix, expected_prefix)
self.assertEqual(result.params, expected_params)
self.assertEqual(result.suffix, expected_suffix)
At the moment, all I have is defining one test per permutation. There must be a better way than this:
class MyPytestTest:
def test_on_on_on(self):
expected_prefix = ...
result = do_magic(...)
assert ...
def test_on_on_off(self):
...
Pytest does not have this functionality natively, but it can be added with the pytest-subtests plugin.
To use it, inject the subtests fixture into the test:
def test_permutations(subtests):
on_off = True, False
for prefix, params, suffix in product(on_off, on_off, on_off):
with subtests.test(prefix=prefix, params=params, suffix=suffix):
result = do_magic(prefix=prefix, params=params, suffix=suffix)
assert ...
In some cases pytest.mark.parametrize may be an appropriate alternative, though it does not work in the same way.
In my unit test I have the following setUp function:
#patch("FrontEnd.FrontEnd.get_cname")
def setUp(self, mock_get_cname):
mock_requests = MagicMock()
mock_requests.return_value.json.side_effect = return_side_effect
mock_requests.return_value.status_code = 200
mock_requests.return_value.text = 'ssl'
self.requests = patch.object(requests, 'get', mock_requests)
self.requests.start()
In one test I have the following:
#patch("requests.get")
def test_search_resource(self, mock_get):
"""Test for _search_resource"""
mock_get.return_value.status_code = 200
self.fe._search_resource("hulahoop")
self.assertTrue(mock_get.call_args,
call('www.example.com/cdn_resources.json?q=hulahoop',
auth=('user', 'password'),
timeout=5))
mock_get.reset_mock()
mock_get.return_value.status_code = 400
with self.assertRaises(Exception):
self.fe._search_resource("hulahoop")
I am repeating the mock_get here although it has been patched in setUp, for the sole reason of being able to assign it different status_code. How do I assign different values to my status_code without patching requests.get again ?