test_example.py: Add new example with mock.patch.

The original mocking example now uses time_machine, so I slimmed
down a lot of its comments, and then I created another
mocking example so that we still touch on mocking in
terms of mock.patch.

The new method reinforces the pattern of testing both the
sad path and happy path inside the same test.
This commit is contained in:
Steve Howell
2025-01-23 11:13:17 +00:00
committed by Tim Abbott
parent d6bd3f7abc
commit 0f8f5fafe1

View File

@@ -5,14 +5,15 @@ import orjson
import time_machine import time_machine
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from zerver.actions.realm_settings import do_set_realm_property
from zerver.actions.users import do_change_can_create_users, do_change_user_role from zerver.actions.users import do_change_can_create_users, do_change_user_role
from zerver.lib.exceptions import JsonableError from zerver.lib.exceptions import JsonableError, StreamWildcardMentionNotAllowedError
from zerver.lib.streams import access_stream_for_send_message from zerver.lib.streams import access_stream_for_send_message
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import most_recent_message from zerver.lib.test_helpers import most_recent_message
from zerver.lib.users import is_administrator_role from zerver.lib.users import is_administrator_role
from zerver.models import UserProfile, UserStatus from zerver.models import Realm, UserProfile, UserStatus
from zerver.models.realms import get_realm from zerver.models.realms import WildcardMentionPolicyEnum, get_realm
from zerver.models.streams import get_stream from zerver.models.streams import get_stream
from zerver.models.users import get_user_by_delivery_email from zerver.models.users import get_user_by_delivery_email
@@ -433,13 +434,75 @@ class TestMocking(ZulipTestCase):
# #
# Mocking is generally used in situations where # Mocking is generally used in situations where
# we want to avoid running original code for reasons # we want to avoid running original code for reasons
# like skipping HTTP requests, saving execution time etc. # like skipping HTTP requests, saving execution time,
# or simulating convenient return values.
# #
# Learn more about mocking in-depth at: # Learn more about mocking in-depth at:
# https://zulip.readthedocs.io/en/latest/testing/testing-with-django.html#testing-with-mocks # https://zulip.readthedocs.io/en/latest/testing/testing-with-django.html#testing-with-mocks
def test_wildcard_mentions(self) -> None:
cordelia = self.example_user("cordelia")
self.login_user(cordelia)
self.subscribe(cordelia, "test_stream")
# Let's explicitly set the policy for sending wildcard
# mentions to a stream. If a stream has too many
# subscribers, we won't allow any users to spam the stream.
do_set_realm_property(
cordelia.realm,
"wildcard_mention_policy",
WildcardMentionPolicyEnum.NOBODY,
acting_user=None,
)
# We will try the same message a couple times.
# Notice the content includes "@**all**".
all_mention = "@**all** test wildcard mention"
# First, let's test the SAD PATH, where cordelia
# tries a wildcard method and gets rejected.
# #
# The following test demonstrates a simple use case # Here we use both mock.patch to simulate a return value
# where mocking is helpful in saving test-run time. # and assertRaisesRegex to verify our function raises
# an error.
with (
mock.patch(
"zerver.lib.message.num_subscribers_for_stream_id",
return_value=Realm.WILDCARD_MENTION_THRESHOLD + 1,
),
self.assertRaisesRegex(
StreamWildcardMentionNotAllowedError,
"You do not have permission to use channel wildcard mentions in this channel.",
),
):
self.send_stream_message(
sender=cordelia,
stream_name="test_stream",
content=all_mention,
)
# Verify the message was NOT sent.
message = most_recent_message(cordelia)
self.assertNotEqual(message.content, all_mention)
# Now for the HAPPY PATH, we still mock the number of
# subscribers, but here we simulate that we are under
# the limit. We expect cordelia's message to go through.
with mock.patch(
"zerver.lib.message.num_subscribers_for_stream_id",
return_value=Realm.WILDCARD_MENTION_THRESHOLD - 1,
):
self.send_stream_message(
sender=cordelia,
stream_name="test_stream",
content=all_mention,
)
# Verify the message WAS sent.
message = most_recent_message(cordelia)
self.assertEqual(message.content, all_mention)
class TestTimeTravel(ZulipTestCase):
def test_edit_message(self) -> None: def test_edit_message(self) -> None:
""" """
Verify if the time limit imposed on message editing is working correctly. Verify if the time limit imposed on message editing is working correctly.
@@ -484,28 +547,15 @@ class TestMocking(ZulipTestCase):
# Now that we tested message editing works within the limit, # Now that we tested message editing works within the limit,
# we want to verify it doesn't work beyond the limit. # we want to verify it doesn't work beyond the limit.
# #
# To do that we'll have to wait for the time limit to pass which is # First, calculate the time we want to travel to, adding
# 5 minutes here. Easy, use time.sleep() but mind that it slows down the # a little buffer of 100 seconds beyond the limit.
# test to a great extent which isn't good. This is when mocking comes to rescue.
# We can check what the original code does to determine whether the time limit
# exceeded and mock that here such that the code runs as if the time limit
# exceeded without actually waiting for that long!
#
# In this case, it is timezone_now, an alias to django.utils.timezone.now,
# to which the difference with message-sent-time is checked. So, we want
# that timezone_now() call to return `datetime` object representing time
# that is beyond the limit.
#
# Notice how mock.patch() is used here to do exactly the above mentioned.
# mock.patch() here makes any calls to `timezone_now` in `zerver.actions.message_edit`
# to return the value passed to `return_value` in the its context.
# You can also use mock.patch() as a decorator depending on the
# requirements. Read more at the documentation link provided above.
time_beyond_edit_limit = message_sent_time + timedelta( time_beyond_edit_limit = message_sent_time + timedelta(
seconds=MESSAGE_CONTENT_EDIT_LIMIT + 100 seconds=MESSAGE_CONTENT_EDIT_LIMIT + 100
) # There's a buffer time applied to the limit, hence the extra 100s. )
# Now use time_machine.travel to simulate that we are in the future.
#
# See https://pypi.org/project/time-machine/ for more info.
with time_machine.travel(time_beyond_edit_limit, tick=False): with time_machine.travel(time_beyond_edit_limit, tick=False):
result = self.client_patch( result = self.client_patch(
f"/json/messages/{sent_message_id}", {"content": "I actually want pizza."} f"/json/messages/{sent_message_id}", {"content": "I actually want pizza."}