bots: Add mock test support for bots using internet services.

Mock requests.get function for bots relying on external services
which may return different results. 'http_response' is mocked to
check if the bots run well and to make the tests determinitic. The
code first checks if the http request corresponding to the http response
is called or not.

This commit renders test files of bots using the internet
(third party) services to Fail. As requests.get() is replaced
by our mock function.

Since we have not provided mock http response for bots like xkcd,
wikipedia, define etc. Their requests.get() function returns empty
message.
This commit is contained in:
Abhijeet Kaur 2017-05-30 04:36:06 +05:30 committed by Tim Abbott
parent 72ddcc1902
commit 919db13fa8

View file

@ -7,6 +7,8 @@ import os
import sys import sys
import unittest import unittest
import logging import logging
import requests
import mock
from mock import MagicMock, patch from mock import MagicMock, patch
@ -17,7 +19,7 @@ from six.moves import zip
from unittest import TestCase from unittest import TestCase
from typing import List, Dict, Any from typing import List, Dict, Any, Optional
from types import ModuleType from types import ModuleType
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
@ -25,8 +27,10 @@ current_dir = os.path.dirname(os.path.abspath(__file__))
class BotTestCase(TestCase): class BotTestCase(TestCase):
bot_name = '' # type: str bot_name = '' # type: str
def check_expected_responses(self, expectations, expected_method='send_reply', email="foo_sender@zulip.com", recipient="foo", subject="foo", type="all"): def check_expected_responses(self, expectations, expected_method='send_reply',
# type: (Dict[str, Any], str, str, str, str, str) -> None email="foo_sender@zulip.com", recipient="foo", subject="foo",
type="all", http_request=None, http_response=None):
# type: (Dict[str, Any], str, str, str, str, str, Dict[str, Any], Dict[str, Any]) -> None
# To test send_message, Any would be a Dict type, # To test send_message, Any would be a Dict type,
# to test send_reply, Any would be a str type. # to test send_reply, Any would be a str type.
if type not in ["private", "stream", "all"]: if type not in ["private", "stream", "all"]:
@ -34,32 +38,29 @@ class BotTestCase(TestCase):
for m, r in expectations.items(): for m, r in expectations.items():
if type != "stream": if type != "stream":
self.mock_test( self.mock_test(
{'content': m, 'type': "private", 'display_recipient': recipient, messages={'content': m, 'type': "private", 'display_recipient': recipient,
'sender_email': email}, r, expected_method) 'sender_email': email}, bot_response=r, expected_method=expected_method,
http_request=http_request, http_response=http_response)
if type != "private": if type != "private":
self.mock_test( self.mock_test(
{'content': m, 'type': "stream", 'display_recipient': recipient, messages={'content': m, 'type': "stream", 'display_recipient': recipient,
'subject': subject, 'sender_email': email}, r, expected_method) 'subject': subject, 'sender_email': email}, bot_response=r,
expected_method=expected_method, http_request=http_request, http_response=http_response)
def mock_test(self, messages, bot_response, expected_method): def mock_test(self, messages, bot_response, expected_method,
# type: (Dict[str, str], Any, str) -> None http_request=None, http_response=None):
if expected_method == "send_reply": # type: (Dict[str, str], Any, str, Dict[str, Any], Dict[str, Any]) -> None
self.mock_test_send_reply(messages, bot_response, expected_method) if expected_method == "send_message":
# Since send_message function uses bot_response of type Dict, no
# further changes required.
self.assert_bot_output(messages=[messages], bot_response=[bot_response], expected_method=expected_method,
http_request=http_request, http_response=http_response)
else: else:
self.mock_test_send_message(messages, bot_response, expected_method) # Since send_reply function uses bot_response of type str, we
# do convert the str type to a Dict type to have the same assert_bot_output function.
def mock_test_send_message(self, messages, bot_response, expected_method): bot_response_type_dict = {'content': bot_response}
# type: (Dict[str, str], Dict[str, str], str) -> None self.assert_bot_output(messages=[messages], bot_response=[bot_response_type_dict], expected_method=expected_method,
# Since send_message function uses bot_response of type Dict, no http_request=http_request, http_response=http_response)
# further changes required.
self.assert_bot_output([messages], [bot_response], expected_method)
def mock_test_send_reply(self, messages, bot_response, expected_method):
# type: (Dict[str, str], str, str) -> None
# Since send_reply function uses bot_response of type str, we
# do convert the str type to a Dict type to have the same assert_bot_output function.
bot_response_type_dict = {'content': bot_response}
self.assert_bot_output([messages], [bot_response_type_dict], expected_method)
def get_bot_message_handler(self): def get_bot_message_handler(self):
# type: () -> Any # type: () -> Any
@ -72,22 +73,35 @@ class BotTestCase(TestCase):
message_handler = self.bot_to_run(bot_module) message_handler = self.bot_to_run(bot_module)
return message_handler return message_handler
def assert_bot_output(self, messages, bot_response, expected_method): def assert_bot_output(self, messages, bot_response, expected_method,
# type: (List[Dict[str, Any]], List[Dict[str, str]], str) -> None http_request=None, http_response=None):
# type: (List[Dict[str, Any]], List[Dict[str, str]], str, Optional[Dict[str, Any]], Optional[Dict[str, Any]]) -> None
message_handler = self.get_bot_message_handler() message_handler = self.get_bot_message_handler()
# Mocking BotHandlerApi # Mocking BotHandlerApi
with patch('bots_api.bot_lib.BotHandlerApi') as MockClass: with patch('bots_api.bot_lib.BotHandlerApi') as MockClass:
instance = MockClass.return_value instance = MockClass.return_value
for (message, response) in zip(messages, bot_response): with patch('requests.get') as mock_get:
# Send message to the concerned bot mock_result = mock.MagicMock()
message_handler.handle_message(message, MockClass(), StateHandler()) mock_result.json.return_value = http_response
# Check if the bot is sending a message via `send_message` function. mock_result.ok.return_value = True
# Where response is a dictionary here. mock_get.return_value = mock_result
if expected_method == "send_message":
instance.send_message.assert_called_with(response) for (message, response) in zip(messages, bot_response):
else: # Send message to the concerned bot
instance.send_reply.assert_called_with(message, response['content']) message_handler.handle_message(message, MockClass(), StateHandler())
# Check if the bot is sending the correct http_request corresponding
# to the given http_response.
if http_request is not None:
mock_get.assert_called_with(http_request['api_url'], params=http_request['params'])
# Check if the bot is sending a message via `send_message` function.
# Where response is a dictionary here.
if expected_method == "send_message":
instance.send_message.assert_called_with(response)
else:
instance.send_reply.assert_called_with(message, response['content'])
def bot_to_run(self, bot_module): def bot_to_run(self, bot_module):
# Returning Any, same argument as in get_bot_message_handler function. # Returning Any, same argument as in get_bot_message_handler function.