Repository: zulip/zulip

Issue:
Migrate all backend tests to use `assertLogs`.
We currently use `mock.patch` approach to verify log outputs. `assertLogs` is a better option than `mock.patch`.
For info on `assertLogs`, see https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLogs

Seeing how  `assertLogs` are used in `test_auth_backends.py`, added in [this commit](https://github.com/zulip/zulip/commit/d30f11888a009dea59711d24696e140d905488c6), might be helpful.


Discussion:
Hello @zulip/server-testing members, this issue was labeled with the "area: testing-infrastructure" label, so you may want to check it out!

<!-- areaLabelAddition -->
@zulipbot claim
Welcome to Zulip, @agupta01! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:
* Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
* [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](https://zulip.readthedocs.io/en/latest/contributing/index.html), as well as the rest of the developer documentation on that site.

See you on the other side (that is, the pull request side)!
Hello @palashcode, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 14 days.

You can reclaim this issue or claim any other issue by commenting `@zulipbot claim` on that issue.

Thanks for your contributions, and hope to see you again soon!
@zulipbot abandon
@zulipbot claim
Welcome to Zulip, @palashcode! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:
* Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
* [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](https://zulip.readthedocs.io/en/latest/contributing/index.html), as well as the rest of the developer documentation on that site.

See you on the other side (that is, the pull request side)!
@zulipbot  claim
@chdinesh1089 I am using your [https://github.com/zulip/zulip/commit/d30f11888a009dea59711d24696e140d905488c6](url) commit as a reference to replace  mock.patch with assertLogs. I made some changes as shown in code below and tested. The test is failing with "AssertionError: no logs of level WARNING or higher triggered on logging.warning". Can you please guide me on what I am doing wrong?

```python
        # with mock.patch("logging.warning") as mock_warn:
        with self.assertLogs("logging.warning", level='WARNING') as m:
            result = self.get_log_into_subdomain(data, force_token='nonsense')
            # mock_warn.assert_called_once_with("log_into_subdomain: Malformed token given: %s", "nonsense")
        self.assertEqual(m.output, [self.logger_output("log_into_subdomain: Malformed token given: nonsense", 'warning')])
        self.assertEqual(result.status_code, 400)
```
`logging.warning` uses root logger, so we do not need to add `"logging.warning"` in `assertLogs` as it defaults to root logger as stated [here](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLogs).
So I think it'll work if we remove `"logging.warning"` and make it ` with self.assertLogs("logging.warning", level='WARNING') as m:`.



> "AssertionError: no logs of level WARNING or higher triggered on logging.warning".

(From this, we can see that `assertLogs` is looking for a logging call with a logger named `logging.warning`, but our actual call is to the root logger)
This could help with any confusion you might probably have https://stackoverflow.com/questions/11225846/what-is-the-difference-between-logging-info-and-logging-getlogger-info 
@chdinesh1089 Thanks. Created a PR [https://github.com/zulip/zulip/pull/15708](url). Please have a look. 
@zulipbot claim
Hello @palashcode, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 14 days.

You can reclaim this issue or claim any other issue by commenting `@zulipbot claim` on that issue.

Thanks for your contributions, and hope to see you again soon!
@zulipbot claim
**ERROR:** You have already claimed this issue.
Following #15708, we're about 180/250 converted:
```
tabbott@coset:~/zulip$ git grep mock[.]patch | grep log | wc
     69     292    7866
tabbott@coset:~/zulip$ git grep assertLogs | wc
    180     992   19059
```
@timabbott are all `mock.patch` migrated to `assertLogs`? If not I would like to work on it.
@shubham00jain are you working on it, because I saw that you have assigned yourself for other issue.

@timabbott from your previous comment I think there is still work to be done on this issue, I have gone through some of the previous commits referencing this issue, from which I think I could work on it.
So I wanted to ask,can I start working on it?
Yeah, go for it!
@zulipbot claim
Hello @m-e-l-u-h-a-n, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 14 days.

You can reclaim this issue or claim any other issue by commenting `@zulipbot claim` on that issue.

Thanks for your contributions, and hope to see you again soon!
Hello, I would like to work on this feature! Can I claim this?
@zulipbot claim
Hello @noabenefraim, it looks like someone has already claimed this issue! Since we believe multiple assignments to the same issue may cause some confusion, we encourage you to search for other unclaimed issues to work on. However, you can always reclaim this issue if no one is working on it.

We look forward to your valuable contributions!
@zulipbot claim
@zulipbot abandon
@zulipbot claim
@zulipbot claim
Hi, @noabenefraim I am continuing to work on this issue even now, I had made some changes  which you can refer in pr #16619 and after getting some more reference from [this comment](https://github.com/zulip/zulip/pull/16619#issuecomment-719064732), I have made more changes to other tests and only 2-3 files are remaining for which I will create a pr soon, so I don't think there would be much to work on it. If you wanted to work on it I would make a pull request with the changes I have made till now. So you may continue over the remaining part, otherwise that may create a confusion on the state of work. I was not able to focus on it as I am having exams these days and they will be over by tomorrow :smiley: .
@m-e-l-u-h-a-n if you're not currently working on this issue, can I continue from where you've left off?
@zulipbot claim
Hello @cmunaco, it looks like you've currently claimed 1 issue in this repository. We encourage new contributors to focus their efforts on at most 1 issue at a time, so please complete your work on your other claimed issues before trying to claim this issue again.

We look forward to your valuable contributions!
@zulipbot claim
@zulipbot claim
@chdinesh1089 
I think this issue should be closed 
can you look into this?
git grep mock[.]patch | grep log | wc 
0  0  0 
git grep assertLogs | wc
271    1478   26981
Hi @gvarun1,
You can use `git grep -n patch | grep '\(logger\|logging\)'`. It will output a lot of lines but I think out of those only:
* `zerver/tests/test_push_notifications.py`
* `zerver/webhooks/github/tests.py`
require migration although if you need to discuss about others you can ask here or in [chat.zulip.org](https://chat.zulip.org). 

You can use #16619 and #16818 for reference.
@m-e-l-u-h-a-n thanks a lot for the references, I will do my best to get my first PR
Yeah, I think @m-e-l-u-h-a-n is right. All the best with your first PR @gvarun1 !
Hello @gvarun1, you claimed this issue to work on it, but this issue and any referenced pull requests haven't been updated for 10 days. Are you still working on this issue?

If so, please update this issue by leaving a comment on this issue to let me know that you're still working on it. Otherwise, I'll automatically remove you from this issue in 4 days.

If you've decided to work on something else, simply comment `@zulipbot abandon` so that someone else can claim it and continue from where you left off.

Thank you for your valuable contributions to Zulip!

<!-- inactiveWarning -->
@zulipbot abandon
Due to some unexpected emergency, I stopped working
@zulipbot claim
@chdinesh1089 Do you need mock.path to be replaced with assertLogs in all the test files?
@chdinesh1089 Can you help me out here? I just need a start.
@zulipbot abandon
@manavdesai27 sorry about the delay. Yes, we want to replace logging mocks(which are done with `mock.patch`) with `assertLogs`. There are only a few as @m-e-l-u-h-a-n [said above](https://github.com/zulip/zulip/issues/15331#issuecomment-862088818).
It would be super nice to finish off those last few and close this out, not least of which because this issue has a bunch of cruft on it (100+ comments, etc.) that make it not super great for new folks to work on.  @chdinesh1089 @m-e-l-u-h-a-n would either of you be up for finishing this?  
I’ll do it.

Resolving patch:
diff --git a/zilencer/management/commands/populate_db.py b/zilencer/management/commands/populate_db.py
--- a/zilencer/management/commands/populate_db.py
+++ b/zilencer/management/commands/populate_db.py
@@ -118,10 +118,6 @@ def clear_database() -> None:
     Session.objects.all().delete()
 
 
-# Suppress spammy output from the push notifications logger
-push_notifications_logger.disabled = True
-
-
 def subscribe_users_to_streams(realm: Realm, stream_dict: Dict[str, Dict[str, Any]]) -> None:
     subscriptions_to_add = []
     event_time = timezone_now()
@@ -273,6 +269,9 @@ def add_arguments(self, parser: CommandParser) -> None:
         )
 
     def handle(self, **options: Any) -> None:
+        # Suppress spammy output from the push notifications logger
+        push_notifications_logger.disabled = True
+
         if options["percent_huddles"] + options["percent_personals"] > 100:
             self.stderr.write("Error!  More than 100% of messages allocated.\n")
             return
@@ -899,6 +898,8 @@ def handle(self, **options: Any) -> None:
             mark_all_messages_as_read()
             self.stdout.write("Successfully populated test database.\n")
 
+        push_notifications_logger.disabled = False
+
 
 def mark_all_messages_as_read() -> None:
     """


Test patch:
diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py
--- a/zerver/lib/test_classes.py
+++ b/zerver/lib/test_classes.py
@@ -1104,7 +1104,9 @@ def simulated_markdown_failure(self) -> Iterator[None]:
         """
         with self.settings(ERROR_BOT=None), mock.patch(
             "zerver.lib.markdown.timeout", side_effect=subprocess.CalledProcessError(1, [])
-        ), mock.patch("zerver.lib.markdown.markdown_logger"):
+        ), self.assertLogs(
+            level="ERROR"
+        ):  # For markdown_logger.exception
             yield
 
     def create_default_device(
diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py
--- a/zerver/tests/test_decorators.py
+++ b/zerver/tests/test_decorators.py
@@ -475,11 +475,11 @@ def my_webhook_raises_exception(request: HttpRequest, user_profile: UserProfile)
         request.body = b"{}"
         request.content_type = "text/plain"
 
-        with mock.patch("zerver.decorator.webhook_logger.exception") as mock_exception:
+        with self.assertLogs("zulip.zerver.webhooks") as logger:
             with self.assertRaisesRegex(Exception, "raised by webhook function"):
                 my_webhook_raises_exception(request)
 
-        mock_exception.assert_called_with("raised by webhook function", stack_info=True)
+        self.assertIn("raised by webhook function", logger.output[0])
 
     def test_authenticated_rest_api_view_logging_unsupported_event(self) -> None:
         @authenticated_rest_api_view(webhook_client_name="ClientName")
diff --git a/zerver/tests/test_middleware.py b/zerver/tests/test_middleware.py
--- a/zerver/tests/test_middleware.py
+++ b/zerver/tests/test_middleware.py
@@ -35,9 +35,11 @@ def test_is_slow_query(self) -> None:
 
     def test_slow_query_log(self) -> None:
         self.log_data["time_started"] = time.time() - self.SLOW_QUERY_TIME
-        with patch("zerver.middleware.slow_query_logger") as mock_slow_query_logger, patch(
-            "zerver.middleware.logger"
-        ) as mock_normal_logger:
+        with self.assertLogs(
+            "zulip.slow_queries", level="INFO"
+        ) as slow_query_logger, self.assertLogs(
+            "zulip.requests", level="INFO"
+        ) as middleware_normal_logger:
 
             write_log_line(
                 self.log_data,
@@ -47,12 +49,11 @@ def test_slow_query_log(self) -> None:
                 requestor_for_logs="unknown",
                 client_name="?",
             )
-            mock_slow_query_logger.info.assert_called_once()
-            mock_normal_logger.info.assert_called_once()
+            self.assert_length(middleware_normal_logger.output, 1)
+            self.assert_length(slow_query_logger.output, 1)
 
-            logged_line = mock_slow_query_logger.info.call_args_list[0][0][0]
             self.assertRegex(
-                logged_line,
+                slow_query_logger.output[0],
                 r"123\.456\.789\.012 GET     200 10\.\ds .* \(unknown via \?\)",
             )
 
diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py
--- a/zerver/tests/test_push_notifications.py
+++ b/zerver/tests/test_push_notifications.py
@@ -8,7 +8,6 @@
 from contextlib import contextmanager
 from typing import Any, Dict, Iterator, List, Optional, Tuple
 from unittest import mock, skipUnless
-from unittest.mock import call
 from urllib import parse
 
 import orjson
@@ -825,11 +824,9 @@ def test_end_to_end(self) -> None:
         }
         with mock.patch(
             "zerver.lib.push_notifications.gcm_client"
-        ) as mock_gcm, self.mock_apns() as mock_apns, mock.patch(
-            "zerver.lib.push_notifications.logger.info"
-        ) as mock_info, mock.patch(
-            "zerver.lib.push_notifications.logger.warning"
-        ):
+        ) as mock_gcm, self.mock_apns() as mock_apns, self.assertLogs(
+            "zerver.lib.push_notifications", level="INFO"
+        ) as logger:
             apns_devices = [
                 (b64_to_hex(device.token), device.ios_app_id, device.token)
                 for device in RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.APNS)
@@ -845,14 +842,15 @@ def test_end_to_end(self) -> None:
             mock_apns.send_notification.return_value.set_result(result)
             handle_push_notification(self.user_profile.id, missed_message)
             for _, _, token in apns_devices:
-                mock_info.assert_any_call(
-                    "APNs: Success sending for user %d to device %s", self.user_profile.id, token
+                self.assertIn(
+                    "INFO:zerver.lib.push_notifications:"
+                    f"APNs: Success sending for user {self.user_profile.id} to device {token}",
+                    logger.output,
                 )
             for _, _, token in gcm_devices:
-                mock_info.assert_any_call(
-                    "GCM: Sent %s as %s",
-                    token,
-                    message.id,
+                self.assertIn(
+                    "INFO:zerver.lib.push_notifications:" f"GCM: Sent {token} as {message.id}",
+                    logger.output,
                 )
 
     @override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
@@ -874,11 +872,9 @@ def test_unregistered_client(self) -> None:
         }
         with mock.patch(
             "zerver.lib.push_notifications.gcm_client"
-        ) as mock_gcm, self.mock_apns() as mock_apns, mock.patch(
-            "zerver.lib.push_notifications.logger.info"
-        ) as mock_info, mock.patch(
-            "zerver.lib.push_notifications.logger.warning"
-        ):
+        ) as mock_gcm, self.mock_apns() as mock_apns, self.assertLogs(
+            "zerver.lib.push_notifications", level="INFO"
+        ) as logger:
             apns_devices = [
                 (b64_to_hex(device.token), device.ios_app_id, device.token)
                 for device in RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.APNS)
@@ -895,10 +891,10 @@ def test_unregistered_client(self) -> None:
             mock_apns.send_notification.return_value.set_result(result)
             handle_push_notification(self.user_profile.id, missed_message)
             for _, _, token in apns_devices:
-                mock_info.assert_any_call(
-                    "APNs: Removing invalid/expired token %s (%s)",
-                    token,
-                    "Unregistered",
+                self.assertIn(
+                    "INFO:zerver.lib.push_notifications:"
+                    f"APNs: Removing invalid/expired token {token} (Unregistered)",
+                    logger.output,
                 )
             self.assertEqual(
                 RemotePushDeviceToken.objects.filter(kind=PushDeviceToken.APNS).count(), 0
@@ -1217,15 +1213,15 @@ def test_user_message_does_not_exist(self) -> None:
         sender = self.example_user("iago")
         message_id = self.send_stream_message(sender, "public_stream", "test")
         missed_message = {"message_id": message_id}
-        with mock.patch("zerver.lib.push_notifications.logger.error") as mock_logger, mock.patch(
+        with self.assertLogs("zerver.lib.push_notifications", level="ERROR") as logger, mock.patch(
             "zerver.lib.push_notifications.push_notifications_enabled", return_value=True
         ) as mock_push_notifications:
             handle_push_notification(self.user_profile.id, missed_message)
-            mock_logger.assert_called_with(
-                "Could not find UserMessage with message_id %s and user_id %s",
-                message_id,
-                self.user_profile.id,
-                exc_info=True,
+            self.assertEqual(
+                "ERROR:zerver.lib.push_notifications:"
+                f"Could not find UserMessage with message_id {message_id} and user_id {self.user_profile.id}"
+                "\nNoneType: None",  # This is an effect of using `exc_info=True` in the actual logger.
+                logger.output[0],
             )
             mock_push_notifications.assert_called_once()
 
@@ -1338,49 +1334,53 @@ def test_get_apns_context(self) -> None:
 
     def test_not_configured(self) -> None:
         self.setup_apns_tokens()
-        with mock.patch("zerver.lib.push_notifications.get_apns_context") as mock_get, mock.patch(
-            "zerver.lib.push_notifications.logger"
-        ) as mock_logging:
+        with mock.patch(
+            "zerver.lib.push_notifications.get_apns_context"
+        ) as mock_get, self.assertLogs("zerver.lib.push_notifications", level="DEBUG") as logger:
             mock_get.return_value = None
             self.send()
-            mock_logging.debug.assert_called_once_with(
+            notification_drop_log = (
+                "DEBUG:zerver.lib.push_notifications:"
                 "APNs: Dropping a notification because nothing configured.  "
                 "Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE)."
             )
-            mock_logging.warning.assert_not_called()
+
             from zerver.lib.push_notifications import initialize_push_notifications
 
             initialize_push_notifications()
-            mock_logging.warning.assert_called_once_with(
+            mobile_notifications_not_configured_log = (
+                "WARNING:zerver.lib.push_notifications:"
                 "Mobile push notifications are not configured.\n  "
                 "See https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html"
             )
 
+            self.assertEqual(
+                [notification_drop_log, mobile_notifications_not_configured_log], logger.output
+            )
+
     def test_success(self) -> None:
         self.setup_apns_tokens()
-        with self.mock_apns() as mock_apns, mock.patch(
-            "zerver.lib.push_notifications.logger"
-        ) as mock_logging:
+        with self.mock_apns() as mock_apns, self.assertLogs(
+            "zerver.lib.push_notifications", level="INFO"
+        ) as logger:
             result = mock.Mock()
             result.is_successful = True
             mock_apns.send_notification.return_value = asyncio.Future()
             mock_apns.send_notification.return_value.set_result(result)
             self.send()
-            mock_logging.warning.assert_not_called()
             for device in self.devices():
-                mock_logging.info.assert_any_call(
-                    "APNs: Success sending for user %d to device %s",
-                    self.user_profile.id,
-                    device.token,
+                self.assertIn(
+                    f"INFO:zerver.lib.push_notifications:APNs: Success sending for user {self.user_profile.id} to device {device.token}",
+                    logger.output,
                 )
 
     def test_http_retry(self) -> None:
         import aioapns
 
         self.setup_apns_tokens()
-        with self.mock_apns() as mock_apns, mock.patch(
-            "zerver.lib.push_notifications.logger"
-        ) as mock_logging:
+        with self.mock_apns() as mock_apns, self.assertLogs(
+            "zerver.lib.push_notifications", level="INFO"
+        ) as logger:
             exception: asyncio.Future[object] = asyncio.Future()
             exception.set_exception(aioapns.exceptions.ConnectionError())
             result = mock.Mock()
@@ -1391,26 +1391,23 @@ def test_http_retry(self) -> None:
                 [exception], itertools.repeat(future)
             )
             self.send()
-            mock_logging.warning.assert_called_once_with(
-                "APNs: ConnectionError sending for user %d to device %s: %s",
-                self.user_profile.id,
-                self.devices()[0].token,
-                "ConnectionError",
+            self.assertIn(
+                f"WARNING:zerver.lib.push_notifications:APNs: ConnectionError sending for user {self.user_profile.id} to device {self.devices()[0].token}: ConnectionError",
+                logger.output,
             )
             for device in self.devices():
-                mock_logging.info.assert_any_call(
-                    "APNs: Success sending for user %d to device %s",
-                    self.user_profile.id,
-                    device.token,
+                self.assertIn(
+                    f"INFO:zerver.lib.push_notifications:APNs: Success sending for user {self.user_profile.id} to device {device.token}",
+                    logger.output,
                 )
 
     def test_http_retry_closed(self) -> None:
         import aioapns
 
         self.setup_apns_tokens()
-        with self.mock_apns() as mock_apns, mock.patch(
-            "zerver.lib.push_notifications.logger"
-        ) as mock_logging:
+        with self.mock_apns() as mock_apns, self.assertLogs(
+            "zerver.lib.push_notifications", level="INFO"
+        ) as logger:
             exception: asyncio.Future[object] = asyncio.Future()
             exception.set_exception(aioapns.exceptions.ConnectionClosed())
             result = mock.Mock()
@@ -1421,39 +1418,40 @@ def test_http_retry_closed(self) -> None:
                 [exception], itertools.repeat(future)
             )
             self.send()
-            mock_logging.warning.assert_called_once_with(
-                "APNs: ConnectionClosed sending for user %d to device %s: %s",
-                self.user_profile.id,
-                self.devices()[0].token,
-                "ConnectionClosed",
+            self.assertIn(
+                f"WARNING:zerver.lib.push_notifications:APNs: ConnectionClosed sending for user {self.user_profile.id} to device {self.devices()[0].token}: ConnectionClosed",
+                logger.output,
             )
             for device in self.devices():
-                mock_logging.info.assert_any_call(
-                    "APNs: Success sending for user %d to device %s",
-                    self.user_profile.id,
-                    device.token,
+                self.assertIn(
+                    f"INFO:zerver.lib.push_notifications:APNs: Success sending for user {self.user_profile.id} to device {device.token}",
+                    logger.output,
                 )
 
     def test_http_retry_eventually_fails(self) -> None:
         import aioapns
 
         self.setup_apns_tokens()
-        with self.mock_apns() as mock_apns, mock.patch(
-            "zerver.lib.push_notifications.logger"
-        ) as mock_logging:
+        with self.mock_apns() as mock_apns, self.assertLogs(
+            "zerver.lib.push_notifications", level="INFO"
+        ) as logger:
             exception: asyncio.Future[object] = asyncio.Future()
             exception.set_exception(aioapns.exceptions.ConnectionError())
             mock_apns.send_notification.side_effect = iter([exception] * 5)
 
             self.send(devices=self.devices()[0:1])
-            self.assertEqual(mock_logging.warning.call_count, 5)
-            mock_logging.warning.assert_called_with(
-                "APNs: Failed to send for user %d to device %s: %s",
-                self.user_profile.id,
-                self.devices()[0].token,
-                "HTTP error, retries exhausted",
+            self.assert_length(
+                [log_record for log_record in logger.records if log_record.levelname == "WARNING"],
+                5,
+            )
+            self.assertIn(
+                f"WARNING:zerver.lib.push_notifications:APNs: Failed to send for user {self.user_profile.id} to device {self.devices()[0].token}: HTTP error, retries exhausted",
+                logger.output,
+            )
+            self.assert_length(
+                [log_record for log_record in logger.records if log_record.levelname == "INFO"],
+                1,
             )
-            self.assertEqual(mock_logging.info.call_count, 1)
 
     def test_modernize_apns_payload(self) -> None:
         payload = {
@@ -2159,57 +2157,55 @@ def get_gcm_data(self, **kwargs: Any) -> Dict[str, Any]:
         data.update(kwargs)
         return data
 
-    @mock.patch("zerver.lib.push_notifications.logger.debug")
-    def test_gcm_is_none(self, mock_debug: mock.MagicMock, mock_gcm: mock.MagicMock) -> None:
+    def test_gcm_is_none(self, mock_gcm: mock.MagicMock) -> None:
         mock_gcm.__bool__.return_value = False
-        send_android_push_notification_to_user(self.user_profile, {}, {})
-        mock_debug.assert_called_with(
-            "Skipping sending a GCM push notification since PUSH_NOTIFICATION_BOUNCER_URL "
-            "and ANDROID_GCM_API_KEY are both unset"
-        )
+        with self.assertLogs("zerver.lib.push_notifications", level="DEBUG") as logger:
+            send_android_push_notification_to_user(self.user_profile, {}, {})
+            self.assertEqual(
+                "DEBUG:zerver.lib.push_notifications:"
+                "Skipping sending a GCM push notification since PUSH_NOTIFICATION_BOUNCER_URL "
+                "and ANDROID_GCM_API_KEY are both unset",
+                logger.output[0],
+            )
 
-    @mock.patch("zerver.lib.push_notifications.logger.warning")
-    def test_json_request_raises_ioerror(
-        self, mock_warn: mock.MagicMock, mock_gcm: mock.MagicMock
-    ) -> None:
+    def test_json_request_raises_ioerror(self, mock_gcm: mock.MagicMock) -> None:
         mock_gcm.json_request.side_effect = IOError("error")
-        send_android_push_notification_to_user(self.user_profile, {}, {})
-        mock_warn.assert_called_with("Error while pushing to GCM", exc_info=True)
+        with self.assertLogs("zerver.lib.push_notifications", level="WARNING") as logger:
+            send_android_push_notification_to_user(self.user_profile, {}, {})
+            self.assertIn(
+                "WARNING:zerver.lib.push_notifications:Error while pushing to GCM\nTraceback ",
+                logger.output[0],
+            )
 
     @mock.patch("zerver.lib.push_notifications.logger.warning")
-    @mock.patch("zerver.lib.push_notifications.logger.info")
-    def test_success(
-        self, mock_info: mock.MagicMock, mock_warning: mock.MagicMock, mock_gcm: mock.MagicMock
-    ) -> None:
+    def test_success(self, mock_warning: mock.MagicMock, mock_gcm: mock.MagicMock) -> None:
         res = {}
         res["success"] = {token: ind for ind, token in enumerate(self.gcm_tokens)}
         mock_gcm.json_request.return_value = res
 
         data = self.get_gcm_data()
-        send_android_push_notification_to_user(self.user_profile, data, {})
-        self.assertEqual(mock_info.call_count, 2)
-        c1 = call("GCM: Sent %s as %s", "1111", 0)
-        c2 = call("GCM: Sent %s as %s", "2222", 1)
-        mock_info.assert_has_calls([c1, c2], any_order=True)
+        with self.assertLogs("zerver.lib.push_notifications", level="INFO") as logger:
+            send_android_push_notification_to_user(self.user_profile, data, {})
+        self.assert_length(logger.output, 2)
+        log_msg1 = f"INFO:zerver.lib.push_notifications:GCM: Sent {1111} as {0}"
+        log_msg2 = f"INFO:zerver.lib.push_notifications:GCM: Sent {2222} as {1}"
+        self.assertEqual([log_msg1, log_msg2], logger.output)
         mock_warning.assert_not_called()
 
-    @mock.patch("zerver.lib.push_notifications.logger.warning")
-    def test_canonical_equal(self, mock_warning: mock.MagicMock, mock_gcm: mock.MagicMock) -> None:
+    def test_canonical_equal(self, mock_gcm: mock.MagicMock) -> None:
         res = {}
         res["canonical"] = {1: 1}
         mock_gcm.json_request.return_value = res
 
         data = self.get_gcm_data()
-        send_android_push_notification_to_user(self.user_profile, data, {})
-        mock_warning.assert_called_once_with(
-            "GCM: Got canonical ref but it already matches our ID %s!",
-            1,
+        with self.assertLogs("zerver.lib.push_notifications", level="WARNING") as logger:
+            send_android_push_notification_to_user(self.user_profile, data, {})
+        self.assertEqual(
+            f"WARNING:zerver.lib.push_notifications:GCM: Got canonical ref but it already matches our ID {1}!",
+            logger.output[0],
         )
 
-    @mock.patch("zerver.lib.push_notifications.logger.warning")
-    def test_canonical_pushdevice_not_present(
-        self, mock_warning: mock.MagicMock, mock_gcm: mock.MagicMock
-    ) -> None:
+    def test_canonical_pushdevice_not_present(self, mock_gcm: mock.MagicMock) -> None:
         res = {}
         t1 = hex_to_b64("1111")
         t2 = hex_to_b64("3333")
@@ -2224,17 +2220,15 @@ def get_count(hex_token: str) -> int:
         self.assertEqual(get_count("3333"), 0)
 
         data = self.get_gcm_data()
-        send_android_push_notification_to_user(self.user_profile, data, {})
-        msg = "GCM: Got canonical ref %s replacing %s but new ID not registered! Updating."
-        mock_warning.assert_called_once_with(msg, t2, t1)
+        with self.assertLogs("zerver.lib.push_notifications", level="WARNING") as logger:
+            send_android_push_notification_to_user(self.user_profile, data, {})
+            msg = f"WARNING:zerver.lib.push_notifications:GCM: Got canonical ref {t2} replacing {t1} but new ID not registered! Updating."
+            self.assertEqual(msg, logger.output[0])
 
         self.assertEqual(get_count("1111"), 0)
         self.assertEqual(get_count("3333"), 1)
 
-    @mock.patch("zerver.lib.push_notifications.logger.info")
-    def test_canonical_pushdevice_different(
-        self, mock_info: mock.MagicMock, mock_gcm: mock.MagicMock
-    ) -> None:
+    def test_canonical_pushdevice_different(self, mock_gcm: mock.MagicMock) -> None:
         res = {}
         old_token = hex_to_b64("1111")
         new_token = hex_to_b64("2222")
@@ -2249,18 +2243,17 @@ def get_count(hex_token: str) -> int:
         self.assertEqual(get_count("2222"), 1)
 
         data = self.get_gcm_data()
-        send_android_push_notification_to_user(self.user_profile, data, {})
-        mock_info.assert_called_once_with(
-            "GCM: Got canonical ref %s, dropping %s",
-            new_token,
-            old_token,
-        )
+        with self.assertLogs("zerver.lib.push_notifications", level="INFO") as logger:
+            send_android_push_notification_to_user(self.user_profile, data, {})
+            self.assertEqual(
+                f"INFO:zerver.lib.push_notifications:GCM: Got canonical ref {new_token}, dropping {old_token}",
+                logger.output[0],
+            )
 
         self.assertEqual(get_count("1111"), 0)
         self.assertEqual(get_count("2222"), 1)
 
-    @mock.patch("zerver.lib.push_notifications.logger.info")
-    def test_not_registered(self, mock_info: mock.MagicMock, mock_gcm: mock.MagicMock) -> None:
+    def test_not_registered(self, mock_gcm: mock.MagicMock) -> None:
         res = {}
         token = hex_to_b64("1111")
         res["errors"] = {"NotRegistered": [token]}
@@ -2273,21 +2266,24 @@ def get_count(hex_token: str) -> int:
         self.assertEqual(get_count("1111"), 1)
 
         data = self.get_gcm_data()
-        send_android_push_notification_to_user(self.user_profile, data, {})
-        mock_info.assert_called_once_with("GCM: Removing %s", token)
+        with self.assertLogs("zerver.lib.push_notifications", level="INFO") as logger:
+            send_android_push_notification_to_user(self.user_profile, data, {})
+            self.assertEqual(
+                f"INFO:zerver.lib.push_notifications:GCM: Removing {token}", logger.output[0]
+            )
         self.assertEqual(get_count("1111"), 0)
 
-    @mock.patch("zerver.lib.push_notifications.logger.warning")
-    def test_failure(self, mock_warn: mock.MagicMock, mock_gcm: mock.MagicMock) -> None:
+    def test_failure(self, mock_gcm: mock.MagicMock) -> None:
         res = {}
         token = hex_to_b64("1111")
         res["errors"] = {"Failed": [token]}
         mock_gcm.json_request.return_value = res
 
         data = self.get_gcm_data()
-        send_android_push_notification_to_user(self.user_profile, data, {})
-        c1 = call("GCM: Delivery to %s failed: %s", token, "Failed")
-        mock_warn.assert_has_calls([c1], any_order=True)
+        with self.assertLogs("zerver.lib.push_notifications", level="WARNING") as logger:
+            send_android_push_notification_to_user(self.user_profile, data, {})
+            msg = f"WARNING:zerver.lib.push_notifications:GCM: Delivery to {token} failed: Failed"
+            self.assertEqual(msg, logger.output[0])
 
 
 class TestClearOnRead(ZulipTestCase):