summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--changelog.d/8617.feature1
-rw-r--r--changelog.d/8734.doc1
-rw-r--r--changelog.d/8751.misc1
-rw-r--r--changelog.d/8754.misc1
-rw-r--r--changelog.d/8757.misc1
-rw-r--r--changelog.d/8758.misc1
-rw-r--r--changelog.d/8759.misc1
-rw-r--r--changelog.d/8760.misc1
-rw-r--r--changelog.d/8761.misc1
-rw-r--r--changelog.d/8765.misc1
-rw-r--r--changelog.d/8770.misc1
-rw-r--r--changelog.d/8771.doc1
-rw-r--r--docs/admin_api/user_admin_api.rst37
-rw-r--r--docs/sample_config.yaml5
-rw-r--r--synapse/api/auth_blocking.py33
-rw-r--r--synapse/config/registration.py5
-rw-r--r--synapse/events/spamcheck.py5
-rw-r--r--synapse/handlers/_base.py4
-rw-r--r--synapse/handlers/auth.py24
-rw-r--r--synapse/handlers/deactivate_account.py5
-rw-r--r--synapse/handlers/federation.py10
-rw-r--r--synapse/handlers/message.py21
-rw-r--r--synapse/handlers/oidc_handler.py92
-rw-r--r--synapse/handlers/presence.py5
-rw-r--r--synapse/handlers/profile.py8
-rw-r--r--synapse/handlers/register.py24
-rw-r--r--synapse/handlers/room.py10
-rw-r--r--synapse/handlers/room_member.py41
-rw-r--r--synapse/handlers/saml_handler.py77
-rw-r--r--synapse/handlers/sso.py90
-rw-r--r--synapse/handlers/sync.py4
-rw-r--r--synapse/module_api/__init__.py5
-rw-r--r--synapse/replication/http/federation.py10
-rw-r--r--synapse/rest/admin/__init__.py2
-rw-r--r--synapse/rest/admin/rooms.py4
-rw-r--r--synapse/rest/admin/users.py54
-rw-r--r--synapse/rest/client/v1/room.py5
-rw-r--r--synapse/rest/client/v2_alpha/sync.py1
-rw-r--r--synapse/server.py5
-rw-r--r--synapse/server_notices/server_notices_manager.py13
-rw-r--r--synapse/storage/databases/main/registration.py2
-rw-r--r--synapse/storage/databases/main/room.py10
-rw-r--r--tests/api/test_auth.py6
-rw-r--r--tests/app/test_frontend_proxy.py9
-rw-r--r--tests/app/test_openid_listener.py15
-rw-r--r--tests/federation/test_complexity.py2
-rw-r--r--tests/federation/test_federation_server.py3
-rw-r--r--tests/federation/transport/test_server.py2
-rw-r--r--tests/handlers/test_directory.py6
-rw-r--r--tests/handlers/test_message.py1
-rw-r--r--tests/handlers/test_oidc.py14
-rw-r--r--tests/handlers/test_sync.py14
-rw-r--r--tests/handlers/test_typing.py1
-rw-r--r--tests/handlers/test_user_directory.py2
-rw-r--r--tests/http/test_additional_resource.py11
-rw-r--r--tests/module_api/test_api.py11
-rw-r--r--tests/replication/_base.py9
-rw-r--r--tests/replication/test_client_reader_shard.py33
-rw-r--r--tests/replication/test_multi_media_repo.py10
-rw-r--r--tests/replication/test_sharded_event_persister.py50
-rw-r--r--tests/rest/admin/test_admin.py39
-rw-r--r--tests/rest/admin/test_device.py35
-rw-r--r--tests/rest/admin/test_event_reports.py27
-rw-r--r--tests/rest/admin/test_media.py42
-rw-r--r--tests/rest/admin/test_room.py46
-rw-r--r--tests/rest/admin/test_statistics.py27
-rw-r--r--tests/rest/admin/test_user.py340
-rw-r--r--tests/rest/client/test_consent.py32
-rw-r--r--tests/rest/client/test_ephemeral_message.py1
-rw-r--r--tests/rest/client/test_identity.py2
-rw-r--r--tests/rest/client/test_redactions.py2
-rw-r--r--tests/rest/client/test_retention.py1
-rw-r--r--tests/rest/client/test_shadow_banned.py8
-rw-r--r--tests/rest/client/test_third_party_rules.py5
-rw-r--r--tests/rest/client/v1/test_directory.py4
-rw-r--r--tests/rest/client/v1/test_events.py4
-rw-r--r--tests/rest/client/v1/test_login.py32
-rw-r--r--tests/rest/client/v1/test_presence.py2
-rw-r--r--tests/rest/client/v1/test_profile.py7
-rw-r--r--tests/rest/client/v1/test_push_rule_attrs.py33
-rw-r--r--tests/rest/client/v1/test_rooms.py107
-rw-r--r--tests/rest/client/v1/test_typing.py4
-rw-r--r--tests/rest/client/v1/utils.py54
-rw-r--r--tests/rest/client/v2_alpha/test_account.py37
-rw-r--r--tests/rest/client/v2_alpha/test_auth.py6
-rw-r--r--tests/rest/client/v2_alpha/test_capabilities.py4
-rw-r--r--tests/rest/client/v2_alpha/test_filter.py7
-rw-r--r--tests/rest/client/v2_alpha/test_password_policy.py8
-rw-r--r--tests/rest/client/v2_alpha/test_register.py31
-rw-r--r--tests/rest/client/v2_alpha/test_relations.py18
-rw-r--r--tests/rest/client/v2_alpha/test_shared_rooms.py1
-rw-r--r--tests/rest/client/v2_alpha/test_sync.py18
-rw-r--r--tests/rest/key/v2/test_remote_key_resource.py8
-rw-r--r--tests/rest/media/v1/test_media_storage.py24
-rw-r--r--tests/rest/media/v1/test_url_preview.py93
-rw-r--r--tests/rest/test_health.py7
-rw-r--r--tests/rest/test_well_known.py8
-rw-r--r--tests/server.py90
-rw-r--r--tests/server_notices/test_consent.py3
-rw-r--r--tests/server_notices/test_resource_limits_server_notices.py3
-rw-r--r--tests/storage/test_cleanup_extrems.py30
-rw-r--r--tests/storage/test_client_ips.py15
-rw-r--r--tests/test_mau.py2
-rw-r--r--tests/test_server.py43
-rw-r--r--tests/test_state.py1
-rw-r--r--tests/test_terms_auth.py3
-rw-r--r--tests/unittest.py54
107 files changed, 1031 insertions, 1159 deletions
diff --git a/changelog.d/8617.feature b/changelog.d/8617.feature
new file mode 100644
index 0000000000..4f1e788506
--- /dev/null
+++ b/changelog.d/8617.feature
@@ -0,0 +1 @@
+Add admin API for logging in as a user.
diff --git a/changelog.d/8734.doc b/changelog.d/8734.doc
new file mode 100644
index 0000000000..3bff9021c7
--- /dev/null
+++ b/changelog.d/8734.doc
@@ -0,0 +1 @@
+Clarify the usecase for an msisdn delegate. Contributed by Adrian Wannenmacher.
diff --git a/changelog.d/8751.misc b/changelog.d/8751.misc
new file mode 100644
index 0000000000..204c280c0e
--- /dev/null
+++ b/changelog.d/8751.misc
@@ -0,0 +1 @@
+Generalise `RoomMemberHandler._locally_reject_invite` to apply to more flows than just invite.
\ No newline at end of file
diff --git a/changelog.d/8754.misc b/changelog.d/8754.misc
new file mode 100644
index 0000000000..0436bb1be7
--- /dev/null
+++ b/changelog.d/8754.misc
@@ -0,0 +1 @@
+Generalise `RoomStore.maybe_store_room_on_invite` to handle other, non-invite membership events.
\ No newline at end of file
diff --git a/changelog.d/8757.misc b/changelog.d/8757.misc
new file mode 100644
index 0000000000..54502e9b90
--- /dev/null
+++ b/changelog.d/8757.misc
@@ -0,0 +1 @@
+Refactor test utilities for injecting HTTP requests.
diff --git a/changelog.d/8758.misc b/changelog.d/8758.misc
new file mode 100644
index 0000000000..54502e9b90
--- /dev/null
+++ b/changelog.d/8758.misc
@@ -0,0 +1 @@
+Refactor test utilities for injecting HTTP requests.
diff --git a/changelog.d/8759.misc b/changelog.d/8759.misc
new file mode 100644
index 0000000000..54502e9b90
--- /dev/null
+++ b/changelog.d/8759.misc
@@ -0,0 +1 @@
+Refactor test utilities for injecting HTTP requests.
diff --git a/changelog.d/8760.misc b/changelog.d/8760.misc
new file mode 100644
index 0000000000..54502e9b90
--- /dev/null
+++ b/changelog.d/8760.misc
@@ -0,0 +1 @@
+Refactor test utilities for injecting HTTP requests.
diff --git a/changelog.d/8761.misc b/changelog.d/8761.misc
new file mode 100644
index 0000000000..e6da7d038d
--- /dev/null
+++ b/changelog.d/8761.misc
@@ -0,0 +1 @@
+ Refactor test utilities for injecting HTTP requests.
diff --git a/changelog.d/8765.misc b/changelog.d/8765.misc
new file mode 100644
index 0000000000..053f9acc9c
--- /dev/null
+++ b/changelog.d/8765.misc
@@ -0,0 +1 @@
+Consolidate logic between the OpenID Connect and SAML code.
diff --git a/changelog.d/8770.misc b/changelog.d/8770.misc
new file mode 100644
index 0000000000..b5876a82f9
--- /dev/null
+++ b/changelog.d/8770.misc
@@ -0,0 +1 @@
+Use `TYPE_CHECKING` instead of magic `MYPY` variable.
diff --git a/changelog.d/8771.doc b/changelog.d/8771.doc
new file mode 100644
index 0000000000..297cf61e98
--- /dev/null
+++ b/changelog.d/8771.doc
@@ -0,0 +1 @@
+Remove extraneous comma from JSON example in User Admin API docs.
\ No newline at end of file
diff --git a/docs/admin_api/user_admin_api.rst b/docs/admin_api/user_admin_api.rst
index d4051d0257..84863296e3 100644
--- a/docs/admin_api/user_admin_api.rst
+++ b/docs/admin_api/user_admin_api.rst
@@ -254,7 +254,7 @@ with a body of:
 
    {
        "new_password": "<secret>",
-       "logout_devices": true,
+       "logout_devices": true
    }
 
 To use it, you will need to authenticate by providing an ``access_token`` for a
@@ -424,6 +424,41 @@ The following fields are returned in the JSON response body:
 - ``next_token``: integer - Indication for pagination. See above.
 - ``total`` - integer - Total number of media.
 
+Login as a user
+===============
+
+Get an access token that can be used to authenticate as that user. Useful for
+when admins wish to do actions on behalf of a user.
+
+The API is::
+
+    POST /_synapse/admin/v1/users/<user_id>/login
+    {}
+
+An optional ``valid_until_ms`` field can be specified in the request body as an
+integer timestamp that specifies when the token should expire. By default tokens
+do not expire.
+
+A response body like the following is returned:
+
+.. code:: json
+
+    {
+        "access_token": "<opaque_access_token_string>"
+    }
+
+
+This API does *not* generate a new device for the user, and so will not appear
+their ``/devices`` list, and in general the target user should not be able to
+tell they have been logged in as.
+
+To expire the token call the standard ``/logout`` API with the token.
+
+Note: The token will expire if the *admin* user calls ``/logout/all`` from any
+of their devices, but the token will *not* expire if the target user does the
+same.
+
+
 User devices
 ============
 
diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml
index c0cd009230..e9e77ca94e 100644
--- a/docs/sample_config.yaml
+++ b/docs/sample_config.yaml
@@ -1230,8 +1230,9 @@ account_validity:
 # email will be globally disabled.
 #
 # Additionally, if `msisdn` is not set, registration and password resets via msisdn
-# will be disabled regardless. This is due to Synapse currently not supporting any
-# method of sending SMS messages on its own.
+# will be disabled regardless, and users will not be able to associate an msisdn
+# identifier to their account. This is due to Synapse currently not supporting
+# any method of sending SMS messages on its own.
 #
 # To enable using an identity server for operations regarding a particular third-party
 # identifier type, set the value to the URL of that identity server as shown in the
diff --git a/synapse/api/auth_blocking.py b/synapse/api/auth_blocking.py
index d8fafd7cb8..9c227218e0 100644
--- a/synapse/api/auth_blocking.py
+++ b/synapse/api/auth_blocking.py
@@ -14,10 +14,12 @@
 # limitations under the License.
 
 import logging
+from typing import Optional
 
 from synapse.api.constants import LimitBlockingTypes, UserTypes
 from synapse.api.errors import Codes, ResourceLimitError
 from synapse.config.server import is_threepid_reserved
+from synapse.types import Requester
 
 logger = logging.getLogger(__name__)
 
@@ -33,24 +35,47 @@ class AuthBlocking:
         self._max_mau_value = hs.config.max_mau_value
         self._limit_usage_by_mau = hs.config.limit_usage_by_mau
         self._mau_limits_reserved_threepids = hs.config.mau_limits_reserved_threepids
+        self._server_name = hs.hostname
 
-    async def check_auth_blocking(self, user_id=None, threepid=None, user_type=None):
+    async def check_auth_blocking(
+        self,
+        user_id: Optional[str] = None,
+        threepid: Optional[dict] = None,
+        user_type: Optional[str] = None,
+        requester: Optional[Requester] = None,
+    ):
         """Checks if the user should be rejected for some external reason,
         such as monthly active user limiting or global disable flag
 
         Args:
-            user_id(str|None): If present, checks for presence against existing
+            user_id: If present, checks for presence against existing
                 MAU cohort
 
-            threepid(dict|None): If present, checks for presence against configured
+            threepid: If present, checks for presence against configured
                 reserved threepid. Used in cases where the user is trying register
                 with a MAU blocked server, normally they would be rejected but their
                 threepid is on the reserved list. user_id and
                 threepid should never be set at the same time.
 
-            user_type(str|None): If present, is used to decide whether to check against
+            user_type: If present, is used to decide whether to check against
                 certain blocking reasons like MAU.
+
+            requester: If present, and the authenticated entity is a user, checks for
+                presence against existing MAU cohort. Passing in both a `user_id` and
+                `requester` is an error.
         """
+        if requester and user_id:
+            raise Exception(
+                "Passed in both 'user_id' and 'requester' to 'check_auth_blocking'"
+            )
+
+        if requester:
+            if requester.authenticated_entity.startswith("@"):
+                user_id = requester.authenticated_entity
+            elif requester.authenticated_entity == self._server_name:
+                # We never block the server from doing actions on behalf of
+                # users.
+                return
 
         # Never fail an auth check for the server notices users or support user
         # This can be a problem where event creation is prohibited due to blocking
diff --git a/synapse/config/registration.py b/synapse/config/registration.py
index b0a77a2e43..cc5f75123c 100644
--- a/synapse/config/registration.py
+++ b/synapse/config/registration.py
@@ -347,8 +347,9 @@ class RegistrationConfig(Config):
         # email will be globally disabled.
         #
         # Additionally, if `msisdn` is not set, registration and password resets via msisdn
-        # will be disabled regardless. This is due to Synapse currently not supporting any
-        # method of sending SMS messages on its own.
+        # will be disabled regardless, and users will not be able to associate an msisdn
+        # identifier to their account. This is due to Synapse currently not supporting
+        # any method of sending SMS messages on its own.
         #
         # To enable using an identity server for operations regarding a particular third-party
         # identifier type, set the value to the URL of that identity server as shown in the
diff --git a/synapse/events/spamcheck.py b/synapse/events/spamcheck.py
index bad18f7fdf..936896656a 100644
--- a/synapse/events/spamcheck.py
+++ b/synapse/events/spamcheck.py
@@ -15,13 +15,12 @@
 # limitations under the License.
 
 import inspect
-from typing import Any, Dict, List, Optional, Tuple
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
 
 from synapse.spam_checker_api import RegistrationBehaviour
 from synapse.types import Collection
 
-MYPY = False
-if MYPY:
+if TYPE_CHECKING:
     import synapse.events
     import synapse.server
 
diff --git a/synapse/handlers/_base.py b/synapse/handlers/_base.py
index bd8e71ae56..bb81c0e81d 100644
--- a/synapse/handlers/_base.py
+++ b/synapse/handlers/_base.py
@@ -169,7 +169,9 @@ class BaseHandler:
                 # and having homeservers have their own users leave keeps more
                 # of that decision-making and control local to the guest-having
                 # homeserver.
-                requester = synapse.types.create_requester(target_user, is_guest=True)
+                requester = synapse.types.create_requester(
+                    target_user, is_guest=True, authenticated_entity=self.server_name
+                )
                 handler = self.hs.get_room_member_handler()
                 await handler.update_membership(
                     requester,
diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py
index 213baea2e3..5163afd86c 100644
--- a/synapse/handlers/auth.py
+++ b/synapse/handlers/auth.py
@@ -698,8 +698,12 @@ class AuthHandler(BaseHandler):
         }
 
     async def get_access_token_for_user_id(
-        self, user_id: str, device_id: Optional[str], valid_until_ms: Optional[int]
-    ):
+        self,
+        user_id: str,
+        device_id: Optional[str],
+        valid_until_ms: Optional[int],
+        puppets_user_id: Optional[str] = None,
+    ) -> str:
         """
         Creates a new access token for the user with the given user ID.
 
@@ -725,13 +729,25 @@ class AuthHandler(BaseHandler):
             fmt_expiry = time.strftime(
                 " until %Y-%m-%d %H:%M:%S", time.localtime(valid_until_ms / 1000.0)
             )
-        logger.info("Logging in user %s on device %s%s", user_id, device_id, fmt_expiry)
+
+        if puppets_user_id:
+            logger.info(
+                "Logging in user %s as %s%s", user_id, puppets_user_id, fmt_expiry
+            )
+        else:
+            logger.info(
+                "Logging in user %s on device %s%s", user_id, device_id, fmt_expiry
+            )
 
         await self.auth.check_auth_blocking(user_id)
 
         access_token = self.macaroon_gen.generate_access_token(user_id)
         await self.store.add_access_token_to_user(
-            user_id, access_token, device_id, valid_until_ms
+            user_id=user_id,
+            token=access_token,
+            device_id=device_id,
+            valid_until_ms=valid_until_ms,
+            puppets_user_id=puppets_user_id,
         )
 
         # the device *should* have been registered before we got here; however,
diff --git a/synapse/handlers/deactivate_account.py b/synapse/handlers/deactivate_account.py
index 4efe6c530a..e808142365 100644
--- a/synapse/handlers/deactivate_account.py
+++ b/synapse/handlers/deactivate_account.py
@@ -39,6 +39,7 @@ class DeactivateAccountHandler(BaseHandler):
         self._room_member_handler = hs.get_room_member_handler()
         self._identity_handler = hs.get_identity_handler()
         self.user_directory_handler = hs.get_user_directory_handler()
+        self._server_name = hs.hostname
 
         # Flag that indicates whether the process to part users from rooms is running
         self._user_parter_running = False
@@ -152,7 +153,7 @@ class DeactivateAccountHandler(BaseHandler):
         for room in pending_invites:
             try:
                 await self._room_member_handler.update_membership(
-                    create_requester(user),
+                    create_requester(user, authenticated_entity=self._server_name),
                     user,
                     room.room_id,
                     "leave",
@@ -208,7 +209,7 @@ class DeactivateAccountHandler(BaseHandler):
             logger.info("User parter parting %r from %r", user_id, room_id)
             try:
                 await self._room_member_handler.update_membership(
-                    create_requester(user),
+                    create_requester(user, authenticated_entity=self._server_name),
                     user,
                     room_id,
                     "leave",
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index c386957706..69bc5ba44d 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -67,7 +67,7 @@ from synapse.replication.http.devices import ReplicationUserDevicesResyncRestSer
 from synapse.replication.http.federation import (
     ReplicationCleanRoomRestServlet,
     ReplicationFederationSendEventsRestServlet,
-    ReplicationStoreRoomOnInviteRestServlet,
+    ReplicationStoreRoomOnOutlierMembershipRestServlet,
 )
 from synapse.state import StateResolutionStore
 from synapse.storage.databases.main.events_worker import EventRedactBehaviour
@@ -152,12 +152,14 @@ class FederationHandler(BaseHandler):
             self._user_device_resync = ReplicationUserDevicesResyncRestServlet.make_client(
                 hs
             )
-            self._maybe_store_room_on_invite = ReplicationStoreRoomOnInviteRestServlet.make_client(
+            self._maybe_store_room_on_outlier_membership = ReplicationStoreRoomOnOutlierMembershipRestServlet.make_client(
                 hs
             )
         else:
             self._device_list_updater = hs.get_device_handler().device_list_updater
-            self._maybe_store_room_on_invite = self.store.maybe_store_room_on_invite
+            self._maybe_store_room_on_outlier_membership = (
+                self.store.maybe_store_room_on_outlier_membership
+            )
 
         # When joining a room we need to queue any events for that room up.
         # For each room, a list of (pdu, origin) tuples.
@@ -1617,7 +1619,7 @@ class FederationHandler(BaseHandler):
         # keep a record of the room version, if we don't yet know it.
         # (this may get overwritten if we later get a different room version in a
         # join dance).
-        await self._maybe_store_room_on_invite(
+        await self._maybe_store_room_on_outlier_membership(
             room_id=event.room_id, room_version=room_version
         )
 
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index c6791fb912..96843338ae 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -472,7 +472,7 @@ class EventCreationHandler:
         Returns:
             Tuple of created event, Context
         """
-        await self.auth.check_auth_blocking(requester.user.to_string())
+        await self.auth.check_auth_blocking(requester=requester)
 
         if event_dict["type"] == EventTypes.Create and event_dict["state_key"] == "":
             room_version = event_dict["content"]["room_version"]
@@ -619,7 +619,13 @@ class EventCreationHandler:
         if requester.app_service is not None:
             return
 
-        user_id = requester.user.to_string()
+        user_id = requester.authenticated_entity
+        if not user_id.startswith("@"):
+            # The authenticated entity might not be a user, e.g. if it's the
+            # server puppetting the user.
+            return
+
+        user = UserID.from_string(user_id)
 
         # exempt the system notices user
         if (
@@ -639,9 +645,7 @@ class EventCreationHandler:
         if u["consent_version"] == self.config.user_consent_version:
             return
 
-        consent_uri = self._consent_uri_builder.build_user_consent_uri(
-            requester.user.localpart
-        )
+        consent_uri = self._consent_uri_builder.build_user_consent_uri(user.localpart)
         msg = self._block_events_without_consent_error % {"consent_uri": consent_uri}
         raise ConsentNotGivenError(msg=msg, consent_uri=consent_uri)
 
@@ -1252,7 +1256,7 @@ class EventCreationHandler:
         for user_id in members:
             if not self.hs.is_mine_id(user_id):
                 continue
-            requester = create_requester(user_id)
+            requester = create_requester(user_id, authenticated_entity=self.server_name)
             try:
                 event, context = await self.create_event(
                     requester,
@@ -1273,11 +1277,6 @@ class EventCreationHandler:
                     requester, event, context, ratelimit=False, ignore_shadow_ban=True,
                 )
                 return True
-            except ConsentNotGivenError:
-                logger.info(
-                    "Failed to send dummy event into room %s for user %s due to "
-                    "lack of consent. Will try another user" % (room_id, user_id)
-                )
             except AuthError:
                 logger.info(
                     "Failed to send dummy event into room %s for user %s due to "
diff --git a/synapse/handlers/oidc_handler.py b/synapse/handlers/oidc_handler.py
index 331d4e7e96..be8562d47b 100644
--- a/synapse/handlers/oidc_handler.py
+++ b/synapse/handlers/oidc_handler.py
@@ -34,7 +34,8 @@ from typing_extensions import TypedDict
 from twisted.web.client import readBody
 
 from synapse.config import ConfigError
-from synapse.http.server import respond_with_html
+from synapse.handlers._base import BaseHandler
+from synapse.handlers.sso import MappingException
 from synapse.http.site import SynapseRequest
 from synapse.logging.context import make_deferred_yieldable
 from synapse.types import JsonDict, UserID, map_username_to_mxid_localpart
@@ -83,17 +84,12 @@ class OidcError(Exception):
         return self.error
 
 
-class MappingException(Exception):
-    """Used to catch errors when mapping the UserInfo object
-    """
-
-
-class OidcHandler:
+class OidcHandler(BaseHandler):
     """Handles requests related to the OpenID Connect login flow.
     """
 
     def __init__(self, hs: "HomeServer"):
-        self.hs = hs
+        super().__init__(hs)
         self._callback_url = hs.config.oidc_callback_url  # type: str
         self._scopes = hs.config.oidc_scopes  # type: List[str]
         self._user_profile_method = hs.config.oidc_user_profile_method  # type: str
@@ -120,36 +116,13 @@ class OidcHandler:
         self._http_client = hs.get_proxied_http_client()
         self._auth_handler = hs.get_auth_handler()
         self._registration_handler = hs.get_registration_handler()
-        self._datastore = hs.get_datastore()
-        self._clock = hs.get_clock()
-        self._hostname = hs.hostname  # type: str
         self._server_name = hs.config.server_name  # type: str
         self._macaroon_secret_key = hs.config.macaroon_secret_key
-        self._error_template = hs.config.sso_error_template
 
         # identifier for the external_ids table
         self._auth_provider_id = "oidc"
 
-    def _render_error(
-        self, request, error: str, error_description: Optional[str] = None
-    ) -> None:
-        """Render the error template and respond to the request with it.
-
-        This is used to show errors to the user. The template of this page can
-        be found under `synapse/res/templates/sso_error.html`.
-
-        Args:
-            request: The incoming request from the browser.
-                We'll respond with an HTML page describing the error.
-            error: A technical identifier for this error. Those include
-                well-known OAuth2/OIDC error types like invalid_request or
-                access_denied.
-            error_description: A human-readable description of the error.
-        """
-        html = self._error_template.render(
-            error=error, error_description=error_description
-        )
-        respond_with_html(request, 400, html)
+        self._sso_handler = hs.get_sso_handler()
 
     def _validate_metadata(self):
         """Verifies the provider metadata.
@@ -571,7 +544,7 @@ class OidcHandler:
 
         Since we might want to display OIDC-related errors in a user-friendly
         way, we don't raise SynapseError from here. Instead, we call
-        ``self._render_error`` which displays an HTML page for the error.
+        ``self._sso_handler.render_error`` which displays an HTML page for the error.
 
         Most of the OpenID Connect logic happens here:
 
@@ -609,7 +582,7 @@ class OidcHandler:
             if error != "access_denied":
                 logger.error("Error from the OIDC provider: %s %s", error, description)
 
-            self._render_error(request, error, description)
+            self._sso_handler.render_error(request, error, description)
             return
 
         # otherwise, it is presumably a successful response. see:
@@ -619,7 +592,9 @@ class OidcHandler:
         session = request.getCookie(SESSION_COOKIE_NAME)  # type: Optional[bytes]
         if session is None:
             logger.info("No session cookie found")
-            self._render_error(request, "missing_session", "No session cookie found")
+            self._sso_handler.render_error(
+                request, "missing_session", "No session cookie found"
+            )
             return
 
         # Remove the cookie. There is a good chance that if the callback failed
@@ -637,7 +612,9 @@ class OidcHandler:
         # Check for the state query parameter
         if b"state" not in request.args:
             logger.info("State parameter is missing")
-            self._render_error(request, "invalid_request", "State parameter is missing")
+            self._sso_handler.render_error(
+                request, "invalid_request", "State parameter is missing"
+            )
             return
 
         state = request.args[b"state"][0].decode()
@@ -651,17 +628,19 @@ class OidcHandler:
             ) = self._verify_oidc_session_token(session, state)
         except MacaroonDeserializationException as e:
             logger.exception("Invalid session")
-            self._render_error(request, "invalid_session", str(e))
+            self._sso_handler.render_error(request, "invalid_session", str(e))
             return
         except MacaroonInvalidSignatureException as e:
             logger.exception("Could not verify session")
-            self._render_error(request, "mismatching_session", str(e))
+            self._sso_handler.render_error(request, "mismatching_session", str(e))
             return
 
         # Exchange the code with the provider
         if b"code" not in request.args:
             logger.info("Code parameter is missing")
-            self._render_error(request, "invalid_request", "Code parameter is missing")
+            self._sso_handler.render_error(
+                request, "invalid_request", "Code parameter is missing"
+            )
             return
 
         logger.debug("Exchanging code")
@@ -670,7 +649,7 @@ class OidcHandler:
             token = await self._exchange_code(code)
         except OidcError as e:
             logger.exception("Could not exchange code")
-            self._render_error(request, e.error, e.error_description)
+            self._sso_handler.render_error(request, e.error, e.error_description)
             return
 
         logger.debug("Successfully obtained OAuth2 access token")
@@ -683,7 +662,7 @@ class OidcHandler:
                 userinfo = await self._fetch_userinfo(token)
             except Exception as e:
                 logger.exception("Could not fetch userinfo")
-                self._render_error(request, "fetch_error", str(e))
+                self._sso_handler.render_error(request, "fetch_error", str(e))
                 return
         else:
             logger.debug("Extracting userinfo from id_token")
@@ -691,7 +670,7 @@ class OidcHandler:
                 userinfo = await self._parse_id_token(token, nonce=nonce)
             except Exception as e:
                 logger.exception("Invalid id_token")
-                self._render_error(request, "invalid_token", str(e))
+                self._sso_handler.render_error(request, "invalid_token", str(e))
                 return
 
         # Pull out the user-agent and IP from the request.
@@ -705,7 +684,7 @@ class OidcHandler:
             )
         except MappingException as e:
             logger.exception("Could not map user")
-            self._render_error(request, "mapping_error", str(e))
+            self._sso_handler.render_error(request, "mapping_error", str(e))
             return
 
         # Mapping providers might not have get_extra_attributes: only call this
@@ -770,7 +749,7 @@ class OidcHandler:
             macaroon.add_first_party_caveat(
                 "ui_auth_session_id = %s" % (ui_auth_session_id,)
             )
-        now = self._clock.time_msec()
+        now = self.clock.time_msec()
         expiry = now + duration_in_ms
         macaroon.add_first_party_caveat("time < %d" % (expiry,))
 
@@ -845,7 +824,7 @@ class OidcHandler:
         if not caveat.startswith(prefix):
             return False
         expiry = int(caveat[len(prefix) :])
-        now = self._clock.time_msec()
+        now = self.clock.time_msec()
         return now < expiry
 
     async def _map_userinfo_to_user(
@@ -885,20 +864,14 @@ class OidcHandler:
         # to be strings.
         remote_user_id = str(remote_user_id)
 
-        logger.info(
-            "Looking for existing mapping for user %s:%s",
-            self._auth_provider_id,
-            remote_user_id,
-        )
-
-        registered_user_id = await self._datastore.get_user_by_external_id(
+        # first of all, check if we already have a mapping for this user
+        previously_registered_user_id = await self._sso_handler.get_sso_user_by_remote_user_id(
             self._auth_provider_id, remote_user_id,
         )
+        if previously_registered_user_id:
+            return previously_registered_user_id
 
-        if registered_user_id is not None:
-            logger.info("Found existing mapping %s", registered_user_id)
-            return registered_user_id
-
+        # Otherwise, generate a new user.
         try:
             attributes = await self._user_mapping_provider.map_user_attributes(
                 userinfo, token
@@ -917,8 +890,8 @@ class OidcHandler:
 
         localpart = map_username_to_mxid_localpart(attributes["localpart"])
 
-        user_id = UserID(localpart, self._hostname).to_string()
-        users = await self._datastore.get_users_by_id_case_insensitive(user_id)
+        user_id = UserID(localpart, self.server_name).to_string()
+        users = await self.store.get_users_by_id_case_insensitive(user_id)
         if users:
             if self._allow_existing_users:
                 if len(users) == 1:
@@ -942,7 +915,8 @@ class OidcHandler:
                 default_display_name=attributes["display_name"],
                 user_agent_ips=(user_agent, ip_address),
             )
-        await self._datastore.record_user_external_id(
+
+        await self.store.record_user_external_id(
             self._auth_provider_id, remote_user_id, registered_user_id,
         )
         return registered_user_id
diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py
index 8e014c9bb5..22d1e9d35c 100644
--- a/synapse/handlers/presence.py
+++ b/synapse/handlers/presence.py
@@ -25,7 +25,7 @@ The methods that define policy are:
 import abc
 import logging
 from contextlib import contextmanager
-from typing import Dict, Iterable, List, Set, Tuple
+from typing import TYPE_CHECKING, Dict, Iterable, List, Set, Tuple
 
 from prometheus_client import Counter
 from typing_extensions import ContextManager
@@ -46,8 +46,7 @@ from synapse.util.caches.descriptors import cached
 from synapse.util.metrics import Measure
 from synapse.util.wheel_timer import WheelTimer
 
-MYPY = False
-if MYPY:
+if TYPE_CHECKING:
     from synapse.server import HomeServer
 
 logger = logging.getLogger(__name__)
diff --git a/synapse/handlers/profile.py b/synapse/handlers/profile.py
index 74a1ddd780..dee0ef45e7 100644
--- a/synapse/handlers/profile.py
+++ b/synapse/handlers/profile.py
@@ -206,7 +206,9 @@ class ProfileHandler(BaseHandler):
         # the join event to update the displayname in the rooms.
         # This must be done by the target user himself.
         if by_admin:
-            requester = create_requester(target_user)
+            requester = create_requester(
+                target_user, authenticated_entity=requester.authenticated_entity,
+            )
 
         await self.store.set_profile_displayname(
             target_user.localpart, displayname_to_set
@@ -286,7 +288,9 @@ class ProfileHandler(BaseHandler):
 
         # Same like set_displayname
         if by_admin:
-            requester = create_requester(target_user)
+            requester = create_requester(
+                target_user, authenticated_entity=requester.authenticated_entity
+            )
 
         await self.store.set_profile_avatar_url(target_user.localpart, new_avatar_url)
 
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
index ed1ff62599..252f700786 100644
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -52,6 +52,7 @@ class RegistrationHandler(BaseHandler):
         self.ratelimiter = hs.get_registration_ratelimiter()
         self.macaroon_gen = hs.get_macaroon_generator()
         self._server_notices_mxid = hs.config.server_notices_mxid
+        self._server_name = hs.hostname
 
         self.spam_checker = hs.get_spam_checker()
 
@@ -317,7 +318,8 @@ class RegistrationHandler(BaseHandler):
         requires_join = False
         if self.hs.config.registration.auto_join_user_id:
             fake_requester = create_requester(
-                self.hs.config.registration.auto_join_user_id
+                self.hs.config.registration.auto_join_user_id,
+                authenticated_entity=self._server_name,
             )
 
             # If the room requires an invite, add the user to the list of invites.
@@ -329,7 +331,9 @@ class RegistrationHandler(BaseHandler):
             # being necessary this will occur after the invite was sent.
             requires_join = True
         else:
-            fake_requester = create_requester(user_id)
+            fake_requester = create_requester(
+                user_id, authenticated_entity=self._server_name
+            )
 
         # Choose whether to federate the new room.
         if not self.hs.config.registration.autocreate_auto_join_rooms_federated:
@@ -362,7 +366,9 @@ class RegistrationHandler(BaseHandler):
                     # created it, then ensure the first user joins it.
                     if requires_join:
                         await room_member_handler.update_membership(
-                            requester=create_requester(user_id),
+                            requester=create_requester(
+                                user_id, authenticated_entity=self._server_name
+                            ),
                             target=UserID.from_string(user_id),
                             room_id=info["room_id"],
                             # Since it was just created, there are no remote hosts.
@@ -370,11 +376,6 @@ class RegistrationHandler(BaseHandler):
                             action="join",
                             ratelimit=False,
                         )
-
-            except ConsentNotGivenError as e:
-                # Technically not necessary to pull out this error though
-                # moving away from bare excepts is a good thing to do.
-                logger.error("Failed to join new user to %r: %r", r, e)
             except Exception as e:
                 logger.error("Failed to join new user to %r: %r", r, e)
 
@@ -426,7 +427,8 @@ class RegistrationHandler(BaseHandler):
                 if requires_invite:
                     await room_member_handler.update_membership(
                         requester=create_requester(
-                            self.hs.config.registration.auto_join_user_id
+                            self.hs.config.registration.auto_join_user_id,
+                            authenticated_entity=self._server_name,
                         ),
                         target=UserID.from_string(user_id),
                         room_id=room_id,
@@ -437,7 +439,9 @@ class RegistrationHandler(BaseHandler):
 
                 # Send the join.
                 await room_member_handler.update_membership(
-                    requester=create_requester(user_id),
+                    requester=create_requester(
+                        user_id, authenticated_entity=self._server_name
+                    ),
                     target=UserID.from_string(user_id),
                     room_id=room_id,
                     remote_room_hosts=remote_room_hosts,
diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index e73031475f..930047e730 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -587,7 +587,7 @@ class RoomCreationHandler(BaseHandler):
         """
         user_id = requester.user.to_string()
 
-        await self.auth.check_auth_blocking(user_id)
+        await self.auth.check_auth_blocking(requester=requester)
 
         if (
             self._server_notices_mxid is not None
@@ -1257,7 +1257,9 @@ class RoomShutdownHandler:
                     400, "User must be our own: %s" % (new_room_user_id,)
                 )
 
-            room_creator_requester = create_requester(new_room_user_id)
+            room_creator_requester = create_requester(
+                new_room_user_id, authenticated_entity=requester_user_id
+            )
 
             info, stream_id = await self._room_creation_handler.create_room(
                 room_creator_requester,
@@ -1297,7 +1299,9 @@ class RoomShutdownHandler:
 
             try:
                 # Kick users from room
-                target_requester = create_requester(user_id)
+                target_requester = create_requester(
+                    user_id, authenticated_entity=requester_user_id
+                )
                 _, stream_id = await self.room_member_handler.update_membership(
                     requester=target_requester,
                     target=target_requester.user,
diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py
index 7cd858b7db..70f8966267 100644
--- a/synapse/handlers/room_member.py
+++ b/synapse/handlers/room_member.py
@@ -965,6 +965,7 @@ class RoomMemberMasterHandler(RoomMemberHandler):
 
         self.distributor = hs.get_distributor()
         self.distributor.declare("user_left_room")
+        self._server_name = hs.hostname
 
     async def _is_remote_room_too_complex(
         self, room_id: str, remote_room_hosts: List[str]
@@ -1059,7 +1060,9 @@ class RoomMemberMasterHandler(RoomMemberHandler):
                 return event_id, stream_id
 
             # The room is too large. Leave.
-            requester = types.create_requester(user, None, False, False, None)
+            requester = types.create_requester(
+                user, authenticated_entity=self._server_name
+            )
             await self.update_membership(
                 requester=requester, target=user, room_id=room_id, action="leave"
             )
@@ -1104,32 +1107,34 @@ class RoomMemberMasterHandler(RoomMemberHandler):
             #
             logger.warning("Failed to reject invite: %s", e)
 
-            return await self._locally_reject_invite(
+            return await self._generate_local_out_of_band_leave(
                 invite_event, txn_id, requester, content
             )
 
-    async def _locally_reject_invite(
+    async def _generate_local_out_of_band_leave(
         self,
-        invite_event: EventBase,
+        previous_membership_event: EventBase,
         txn_id: Optional[str],
         requester: Requester,
         content: JsonDict,
     ) -> Tuple[str, int]:
-        """Generate a local invite rejection
+        """Generate a local leave event for a room
 
-        This is called after we fail to reject an invite via a remote server. It
-        generates an out-of-band membership event locally.
+        This can be called after we e.g fail to reject an invite via a remote server.
+        It generates an out-of-band membership event locally.
 
         Args:
-            invite_event: the invite to be rejected
+            previous_membership_event: the previous membership event for this user
             txn_id: optional transaction ID supplied by the client
-            requester:  user making the rejection request, according to the access token
-            content: additional content to include in the rejection event.
+            requester: user making the request, according to the access token
+            content: additional content to include in the leave event.
                Normally an empty dict.
-        """
 
-        room_id = invite_event.room_id
-        target_user = invite_event.state_key
+        Returns:
+            A tuple containing (event_id, stream_id of the leave event)
+        """
+        room_id = previous_membership_event.room_id
+        target_user = previous_membership_event.state_key
 
         content["membership"] = Membership.LEAVE
 
@@ -1141,12 +1146,12 @@ class RoomMemberMasterHandler(RoomMemberHandler):
             "state_key": target_user,
         }
 
-        # the auth events for the new event are the same as that of the invite, plus
-        # the invite itself.
+        # the auth events for the new event are the same as that of the previous event, plus
+        # the event itself.
         #
-        # the prev_events are just the invite.
-        prev_event_ids = [invite_event.event_id]
-        auth_event_ids = invite_event.auth_event_ids() + prev_event_ids
+        # the prev_events consist solely of the previous membership event.
+        prev_event_ids = [previous_membership_event.event_id]
+        auth_event_ids = previous_membership_event.auth_event_ids() + prev_event_ids
 
         event, context = await self.event_creation_handler.create_event(
             requester,
diff --git a/synapse/handlers/saml_handler.py b/synapse/handlers/saml_handler.py
index fd6c5e9ea8..aee772239a 100644
--- a/synapse/handlers/saml_handler.py
+++ b/synapse/handlers/saml_handler.py
@@ -24,7 +24,8 @@ from saml2.client import Saml2Client
 from synapse.api.errors import SynapseError
 from synapse.config import ConfigError
 from synapse.config.saml2_config import SamlAttributeRequirement
-from synapse.http.server import respond_with_html
+from synapse.handlers._base import BaseHandler
+from synapse.handlers.sso import MappingException
 from synapse.http.servlet import parse_string
 from synapse.http.site import SynapseRequest
 from synapse.module_api import ModuleApi
@@ -42,10 +43,6 @@ if TYPE_CHECKING:
 logger = logging.getLogger(__name__)
 
 
-class MappingException(Exception):
-    """Used to catch errors when mapping the SAML2 response to a user."""
-
-
 @attr.s(slots=True)
 class Saml2SessionData:
     """Data we track about SAML2 sessions"""
@@ -57,17 +54,13 @@ class Saml2SessionData:
     ui_auth_session_id = attr.ib(type=Optional[str], default=None)
 
 
-class SamlHandler:
+class SamlHandler(BaseHandler):
     def __init__(self, hs: "synapse.server.HomeServer"):
-        self.hs = hs
+        super().__init__(hs)
         self._saml_client = Saml2Client(hs.config.saml2_sp_config)
-        self._auth = hs.get_auth()
         self._auth_handler = hs.get_auth_handler()
         self._registration_handler = hs.get_registration_handler()
 
-        self._clock = hs.get_clock()
-        self._datastore = hs.get_datastore()
-        self._hostname = hs.hostname
         self._saml2_session_lifetime = hs.config.saml2_session_lifetime
         self._grandfathered_mxid_source_attribute = (
             hs.config.saml2_grandfathered_mxid_source_attribute
@@ -88,26 +81,9 @@ class SamlHandler:
         self._outstanding_requests_dict = {}  # type: Dict[str, Saml2SessionData]
 
         # a lock on the mappings
-        self._mapping_lock = Linearizer(name="saml_mapping", clock=self._clock)
-
-    def _render_error(
-        self, request, error: str, error_description: Optional[str] = None
-    ) -> None:
-        """Render the error template and respond to the request with it.
+        self._mapping_lock = Linearizer(name="saml_mapping", clock=self.clock)
 
-        This is used to show errors to the user. The template of this page can
-        be found under `synapse/res/templates/sso_error.html`.
-
-        Args:
-            request: The incoming request from the browser.
-                We'll respond with an HTML page describing the error.
-            error: A technical identifier for this error.
-            error_description: A human-readable description of the error.
-        """
-        html = self._error_template.render(
-            error=error, error_description=error_description
-        )
-        respond_with_html(request, 400, html)
+        self._sso_handler = hs.get_sso_handler()
 
     def handle_redirect_request(
         self, client_redirect_url: bytes, ui_auth_session_id: Optional[str] = None
@@ -130,7 +106,7 @@ class SamlHandler:
         # Since SAML sessions timeout it is useful to log when they were created.
         logger.info("Initiating a new SAML session: %s" % (reqid,))
 
-        now = self._clock.time_msec()
+        now = self.clock.time_msec()
         self._outstanding_requests_dict[reqid] = Saml2SessionData(
             creation_time=now, ui_auth_session_id=ui_auth_session_id,
         )
@@ -171,12 +147,12 @@ class SamlHandler:
             # in the (user-visible) exception message, so let's log the exception here
             # so we can track down the session IDs later.
             logger.warning(str(e))
-            self._render_error(
+            self._sso_handler.render_error(
                 request, "unsolicited_response", "Unexpected SAML2 login."
             )
             return
         except Exception as e:
-            self._render_error(
+            self._sso_handler.render_error(
                 request,
                 "invalid_response",
                 "Unable to parse SAML2 response: %s." % (e,),
@@ -184,7 +160,7 @@ class SamlHandler:
             return
 
         if saml2_auth.not_signed:
-            self._render_error(
+            self._sso_handler.render_error(
                 request, "unsigned_respond", "SAML2 response was not signed."
             )
             return
@@ -210,7 +186,7 @@ class SamlHandler:
         # attributes.
         for requirement in self._saml2_attribute_requirements:
             if not _check_attribute_requirement(saml2_auth.ava, requirement):
-                self._render_error(
+                self._sso_handler.render_error(
                     request, "unauthorised", "You are not authorised to log in here."
                 )
                 return
@@ -226,7 +202,7 @@ class SamlHandler:
             )
         except MappingException as e:
             logger.exception("Could not map user")
-            self._render_error(request, "mapping_error", str(e))
+            self._sso_handler.render_error(request, "mapping_error", str(e))
             return
 
         # Complete the interactive auth session or the login.
@@ -274,17 +250,11 @@ class SamlHandler:
 
         with (await self._mapping_lock.queue(self._auth_provider_id)):
             # first of all, check if we already have a mapping for this user
-            logger.info(
-                "Looking for existing mapping for user %s:%s",
-                self._auth_provider_id,
-                remote_user_id,
+            previously_registered_user_id = await self._sso_handler.get_sso_user_by_remote_user_id(
+                self._auth_provider_id, remote_user_id,
             )
-            registered_user_id = await self._datastore.get_user_by_external_id(
-                self._auth_provider_id, remote_user_id
-            )
-            if registered_user_id is not None:
-                logger.info("Found existing mapping %s", registered_user_id)
-                return registered_user_id
+            if previously_registered_user_id:
+                return previously_registered_user_id
 
             # backwards-compatibility hack: see if there is an existing user with a
             # suitable mapping from the uid
@@ -294,7 +264,7 @@ class SamlHandler:
             ):
                 attrval = saml2_auth.ava[self._grandfathered_mxid_source_attribute][0]
                 user_id = UserID(
-                    map_username_to_mxid_localpart(attrval), self._hostname
+                    map_username_to_mxid_localpart(attrval), self.server_name
                 ).to_string()
                 logger.info(
                     "Looking for existing account based on mapped %s %s",
@@ -302,11 +272,11 @@ class SamlHandler:
                     user_id,
                 )
 
-                users = await self._datastore.get_users_by_id_case_insensitive(user_id)
+                users = await self.store.get_users_by_id_case_insensitive(user_id)
                 if users:
                     registered_user_id = list(users.keys())[0]
                     logger.info("Grandfathering mapping to %s", registered_user_id)
-                    await self._datastore.record_user_external_id(
+                    await self.store.record_user_external_id(
                         self._auth_provider_id, remote_user_id, registered_user_id
                     )
                     return registered_user_id
@@ -335,8 +305,8 @@ class SamlHandler:
                 emails = attribute_dict.get("emails", [])
 
                 # Check if this mxid already exists
-                if not await self._datastore.get_users_by_id_case_insensitive(
-                    UserID(localpart, self._hostname).to_string()
+                if not await self.store.get_users_by_id_case_insensitive(
+                    UserID(localpart, self.server_name).to_string()
                 ):
                     # This mxid is free
                     break
@@ -348,7 +318,6 @@ class SamlHandler:
                 )
 
             logger.info("Mapped SAML user to local part %s", localpart)
-
             registered_user_id = await self._registration_handler.register_user(
                 localpart=localpart,
                 default_display_name=displayname,
@@ -356,13 +325,13 @@ class SamlHandler:
                 user_agent_ips=(user_agent, ip_address),
             )
 
-            await self._datastore.record_user_external_id(
+            await self.store.record_user_external_id(
                 self._auth_provider_id, remote_user_id, registered_user_id
             )
             return registered_user_id
 
     def expire_sessions(self):
-        expire_before = self._clock.time_msec() - self._saml2_session_lifetime
+        expire_before = self.clock.time_msec() - self._saml2_session_lifetime
         to_expire = set()
         for reqid, data in self._outstanding_requests_dict.items():
             if data.creation_time < expire_before:
diff --git a/synapse/handlers/sso.py b/synapse/handlers/sso.py
new file mode 100644
index 0000000000..9cb1866a71
--- /dev/null
+++ b/synapse/handlers/sso.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Matrix.org Foundation C.I.C.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import logging
+from typing import TYPE_CHECKING, Optional
+
+from synapse.handlers._base import BaseHandler
+from synapse.http.server import respond_with_html
+
+if TYPE_CHECKING:
+    from synapse.server import HomeServer
+
+logger = logging.getLogger(__name__)
+
+
+class MappingException(Exception):
+    """Used to catch errors when mapping the UserInfo object
+    """
+
+
+class SsoHandler(BaseHandler):
+    def __init__(self, hs: "HomeServer"):
+        super().__init__(hs)
+        self._error_template = hs.config.sso_error_template
+
+    def render_error(
+        self, request, error: str, error_description: Optional[str] = None
+    ) -> None:
+        """Renders the error template and responds with it.
+
+        This is used to show errors to the user. The template of this page can
+        be found under `synapse/res/templates/sso_error.html`.
+
+        Args:
+            request: The incoming request from the browser.
+                We'll respond with an HTML page describing the error.
+            error: A technical identifier for this error.
+            error_description: A human-readable description of the error.
+        """
+        html = self._error_template.render(
+            error=error, error_description=error_description
+        )
+        respond_with_html(request, 400, html)
+
+    async def get_sso_user_by_remote_user_id(
+        self, auth_provider_id: str, remote_user_id: str
+    ) -> Optional[str]:
+        """
+        Maps the user ID of a remote IdP to a mxid for a previously seen user.
+
+        If the user has not been seen yet, this will return None.
+
+        Args:
+            auth_provider_id: A unique identifier for this SSO provider, e.g.
+                "oidc" or "saml".
+            remote_user_id: The user ID according to the remote IdP. This might
+                be an e-mail address, a GUID, or some other form. It must be
+                unique and immutable.
+
+        Returns:
+            The mxid of a previously seen user.
+        """
+        # Check if we already have a mapping for this user.
+        logger.info(
+            "Looking for existing mapping for user %s:%s",
+            auth_provider_id,
+            remote_user_id,
+        )
+        previously_registered_user_id = await self.store.get_user_by_external_id(
+            auth_provider_id, remote_user_id,
+        )
+
+        # A match was found, return the user ID.
+        if previously_registered_user_id is not None:
+            logger.info("Found existing mapping %s", previously_registered_user_id)
+            return previously_registered_user_id
+
+        # No match.
+        return None
diff --git a/synapse/handlers/sync.py b/synapse/handlers/sync.py
index 32e53c2d25..9827c7eb8d 100644
--- a/synapse/handlers/sync.py
+++ b/synapse/handlers/sync.py
@@ -31,6 +31,7 @@ from synapse.types import (
     Collection,
     JsonDict,
     MutableStateMap,
+    Requester,
     RoomStreamToken,
     StateMap,
     StreamToken,
@@ -260,6 +261,7 @@ class SyncHandler:
 
     async def wait_for_sync_for_user(
         self,
+        requester: Requester,
         sync_config: SyncConfig,
         since_token: Optional[StreamToken] = None,
         timeout: int = 0,
@@ -273,7 +275,7 @@ class SyncHandler:
         # not been exceeded (if not part of the group by this point, almost certain
         # auth_blocking will occur)
         user_id = sync_config.user.to_string()
-        await self.auth.check_auth_blocking(user_id)
+        await self.auth.check_auth_blocking(requester=requester)
 
         res = await self.response_cache.wrap(
             sync_config.request_key,
diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py
index 0142542852..72ab5750cc 100644
--- a/synapse/module_api/__init__.py
+++ b/synapse/module_api/__init__.py
@@ -49,6 +49,7 @@ class ModuleApi:
         self._store = hs.get_datastore()
         self._auth = hs.get_auth()
         self._auth_handler = auth_handler
+        self._server_name = hs.hostname
 
         # We expose these as properties below in order to attach a helpful docstring.
         self._http_client = hs.get_simple_http_client()  # type: SimpleHttpClient
@@ -336,7 +337,9 @@ class ModuleApi:
             SynapseError if the event was not allowed.
         """
         # Create a requester object
-        requester = create_requester(event_dict["sender"])
+        requester = create_requester(
+            event_dict["sender"], authenticated_entity=self._server_name
+        )
 
         # Create and send the event
         (
diff --git a/synapse/replication/http/federation.py b/synapse/replication/http/federation.py
index b4f4a68b5c..7a0dbb5b1a 100644
--- a/synapse/replication/http/federation.py
+++ b/synapse/replication/http/federation.py
@@ -254,20 +254,20 @@ class ReplicationCleanRoomRestServlet(ReplicationEndpoint):
         return 200, {}
 
 
-class ReplicationStoreRoomOnInviteRestServlet(ReplicationEndpoint):
+class ReplicationStoreRoomOnOutlierMembershipRestServlet(ReplicationEndpoint):
     """Called to clean up any data in DB for a given room, ready for the
     server to join the room.
 
     Request format:
 
-        POST /_synapse/replication/store_room_on_invite/:room_id/:txn_id
+        POST /_synapse/replication/store_room_on_outlier_membership/:room_id/:txn_id
 
         {
             "room_version": "1",
         }
     """
 
-    NAME = "store_room_on_invite"
+    NAME = "store_room_on_outlier_membership"
     PATH_ARGS = ("room_id",)
 
     def __init__(self, hs):
@@ -282,7 +282,7 @@ class ReplicationStoreRoomOnInviteRestServlet(ReplicationEndpoint):
     async def _handle_request(self, request, room_id):
         content = parse_json_object_from_request(request)
         room_version = KNOWN_ROOM_VERSIONS[content["room_version"]]
-        await self.store.maybe_store_room_on_invite(room_id, room_version)
+        await self.store.maybe_store_room_on_outlier_membership(room_id, room_version)
         return 200, {}
 
 
@@ -291,4 +291,4 @@ def register_servlets(hs, http_server):
     ReplicationFederationSendEduRestServlet(hs).register(http_server)
     ReplicationGetQueryRestServlet(hs).register(http_server)
     ReplicationCleanRoomRestServlet(hs).register(http_server)
-    ReplicationStoreRoomOnInviteRestServlet(hs).register(http_server)
+    ReplicationStoreRoomOnOutlierMembershipRestServlet(hs).register(http_server)
diff --git a/synapse/rest/admin/__init__.py b/synapse/rest/admin/__init__.py
index 2a4f7a1740..7a3a5c46ca 100644
--- a/synapse/rest/admin/__init__.py
+++ b/synapse/rest/admin/__init__.py
@@ -61,6 +61,7 @@ from synapse.rest.admin.users import (
     UserRestServletV2,
     UsersRestServlet,
     UsersRestServletV2,
+    UserTokenRestServlet,
     WhoisRestServlet,
 )
 from synapse.types import RoomStreamToken
@@ -223,6 +224,7 @@ def register_servlets(hs, http_server):
     UserAdminServlet(hs).register(http_server)
     UserMediaRestServlet(hs).register(http_server)
     UserMembershipRestServlet(hs).register(http_server)
+    UserTokenRestServlet(hs).register(http_server)
     UserRestServletV2(hs).register(http_server)
     UsersRestServletV2(hs).register(http_server)
     DeviceRestServlet(hs).register(http_server)
diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py
index f5304ff43d..ee345e12ce 100644
--- a/synapse/rest/admin/rooms.py
+++ b/synapse/rest/admin/rooms.py
@@ -309,7 +309,9 @@ class JoinRoomAliasServlet(RestServlet):
                 400, "%s was not legal room ID or room alias" % (room_identifier,)
             )
 
-        fake_requester = create_requester(target_user)
+        fake_requester = create_requester(
+            target_user, authenticated_entity=requester.authenticated_entity
+        )
 
         # send invite if room has "JoinRules.INVITE"
         room_state = await self.state_handler.get_current_state(room_id)
diff --git a/synapse/rest/admin/users.py b/synapse/rest/admin/users.py
index 3638e219f2..fa8d8e6d91 100644
--- a/synapse/rest/admin/users.py
+++ b/synapse/rest/admin/users.py
@@ -16,7 +16,7 @@ import hashlib
 import hmac
 import logging
 from http import HTTPStatus
-from typing import Tuple
+from typing import TYPE_CHECKING, Tuple
 
 from synapse.api.constants import UserTypes
 from synapse.api.errors import Codes, NotFoundError, SynapseError
@@ -37,6 +37,9 @@ from synapse.rest.admin._base import (
 )
 from synapse.types import JsonDict, UserID
 
+if TYPE_CHECKING:
+    from synapse.server import HomeServer
+
 logger = logging.getLogger(__name__)
 
 _GET_PUSHERS_ALLOWED_KEYS = {
@@ -828,3 +831,52 @@ class UserMediaRestServlet(RestServlet):
             ret["next_token"] = start + len(media)
 
         return 200, ret
+
+
+class UserTokenRestServlet(RestServlet):
+    """An admin API for logging in as a user.
+
+    Example:
+
+        POST /_synapse/admin/v1/users/@test:example.com/login
+        {}
+
+        200 OK
+        {
+            "access_token": "<some_token>"
+        }
+    """
+
+    PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/login$")
+
+    def __init__(self, hs: "HomeServer"):
+        self.hs = hs
+        self.store = hs.get_datastore()
+        self.auth = hs.get_auth()
+        self.auth_handler = hs.get_auth_handler()
+
+    async def on_POST(self, request, user_id):
+        requester = await self.auth.get_user_by_req(request)
+        await assert_user_is_admin(self.auth, requester.user)
+        auth_user = requester.user
+
+        if not self.hs.is_mine_id(user_id):
+            raise SynapseError(400, "Only local users can be logged in as")
+
+        body = parse_json_object_from_request(request, allow_empty_body=True)
+
+        valid_until_ms = body.get("valid_until_ms")
+        if valid_until_ms and not isinstance(valid_until_ms, int):
+            raise SynapseError(400, "'valid_until_ms' parameter must be an int")
+
+        if auth_user.to_string() == user_id:
+            raise SynapseError(400, "Cannot use admin API to login as self")
+
+        token = await self.auth_handler.get_access_token_for_user_id(
+            user_id=auth_user.to_string(),
+            device_id=None,
+            valid_until_ms=valid_until_ms,
+            puppets_user_id=user_id,
+        )
+
+        return 200, {"access_token": token}
diff --git a/synapse/rest/client/v1/room.py b/synapse/rest/client/v1/room.py
index 25d3cc6148..93c06afe27 100644
--- a/synapse/rest/client/v1/room.py
+++ b/synapse/rest/client/v1/room.py
@@ -18,7 +18,7 @@
 
 import logging
 import re
-from typing import List, Optional
+from typing import TYPE_CHECKING, List, Optional
 from urllib import parse as urlparse
 
 from synapse.api.constants import EventTypes, Membership
@@ -48,8 +48,7 @@ from synapse.types import RoomAlias, RoomID, StreamToken, ThirdPartyInstanceID,
 from synapse.util import json_decoder
 from synapse.util.stringutils import random_string
 
-MYPY = False
-if MYPY:
+if TYPE_CHECKING:
     import synapse.server
 
 logger = logging.getLogger(__name__)
diff --git a/synapse/rest/client/v2_alpha/sync.py b/synapse/rest/client/v2_alpha/sync.py
index 2b84eb89c0..8e52e4cca4 100644
--- a/synapse/rest/client/v2_alpha/sync.py
+++ b/synapse/rest/client/v2_alpha/sync.py
@@ -171,6 +171,7 @@ class SyncRestServlet(RestServlet):
         )
         with context:
             sync_result = await self.sync_handler.wait_for_sync_for_user(
+                requester,
                 sync_config,
                 since_token=since_token,
                 timeout=timeout,
diff --git a/synapse/server.py b/synapse/server.py
index 21a232bbd9..12a783de17 100644
--- a/synapse/server.py
+++ b/synapse/server.py
@@ -89,6 +89,7 @@ from synapse.handlers.room_member import RoomMemberMasterHandler
 from synapse.handlers.room_member_worker import RoomMemberWorkerHandler
 from synapse.handlers.search import SearchHandler
 from synapse.handlers.set_password import SetPasswordHandler
+from synapse.handlers.sso import SsoHandler
 from synapse.handlers.stats import StatsHandler
 from synapse.handlers.sync import SyncHandler
 from synapse.handlers.typing import FollowerTypingHandler, TypingWriterHandler
@@ -391,6 +392,10 @@ class HomeServer(metaclass=abc.ABCMeta):
             return FollowerTypingHandler(self)
 
     @cache_in_self
+    def get_sso_handler(self) -> SsoHandler:
+        return SsoHandler(self)
+
+    @cache_in_self
     def get_sync_handler(self) -> SyncHandler:
         return SyncHandler(self)
 
diff --git a/synapse/server_notices/server_notices_manager.py b/synapse/server_notices/server_notices_manager.py
index d464c75c03..100dbd5e2c 100644
--- a/synapse/server_notices/server_notices_manager.py
+++ b/synapse/server_notices/server_notices_manager.py
@@ -39,6 +39,7 @@ class ServerNoticesManager:
         self._room_member_handler = hs.get_room_member_handler()
         self._event_creation_handler = hs.get_event_creation_handler()
         self._is_mine_id = hs.is_mine_id
+        self._server_name = hs.hostname
 
         self._notifier = hs.get_notifier()
         self.server_notices_mxid = self._config.server_notices_mxid
@@ -72,7 +73,9 @@ class ServerNoticesManager:
         await self.maybe_invite_user_to_room(user_id, room_id)
 
         system_mxid = self._config.server_notices_mxid
-        requester = create_requester(system_mxid)
+        requester = create_requester(
+            system_mxid, authenticated_entity=self._server_name
+        )
 
         logger.info("Sending server notice to %s", user_id)
 
@@ -145,7 +148,9 @@ class ServerNoticesManager:
                 "avatar_url": self._config.server_notices_mxid_avatar_url,
             }
 
-        requester = create_requester(self.server_notices_mxid)
+        requester = create_requester(
+            self.server_notices_mxid, authenticated_entity=self._server_name
+        )
         info, _ = await self._room_creation_handler.create_room(
             requester,
             config={
@@ -174,7 +179,9 @@ class ServerNoticesManager:
             user_id: The ID of the user to invite.
             room_id: The ID of the room to invite the user to.
         """
-        requester = create_requester(self.server_notices_mxid)
+        requester = create_requester(
+            self.server_notices_mxid, authenticated_entity=self._server_name
+        )
 
         # Check whether the user has already joined or been invited to this room. If
         # that's the case, there is no need to re-invite them.
diff --git a/synapse/storage/databases/main/registration.py b/synapse/storage/databases/main/registration.py
index e5d07ce72a..fedb8a6c26 100644
--- a/synapse/storage/databases/main/registration.py
+++ b/synapse/storage/databases/main/registration.py
@@ -1110,6 +1110,7 @@ class RegistrationStore(StatsStore, RegistrationBackgroundUpdateStore):
         token: str,
         device_id: Optional[str],
         valid_until_ms: Optional[int],
+        puppets_user_id: Optional[str] = None,
     ) -> int:
         """Adds an access token for the given user.
 
@@ -1133,6 +1134,7 @@ class RegistrationStore(StatsStore, RegistrationBackgroundUpdateStore):
                 "token": token,
                 "device_id": device_id,
                 "valid_until_ms": valid_until_ms,
+                "puppets_user_id": puppets_user_id,
             },
             desc="add_access_token_to_user",
         )
diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py
index dc0c4b5499..6b89db15c9 100644
--- a/synapse/storage/databases/main/room.py
+++ b/synapse/storage/databases/main/room.py
@@ -1240,13 +1240,15 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore, SearchStore):
             logger.error("store_room with room_id=%s failed: %s", room_id, e)
             raise StoreError(500, "Problem creating room.")
 
-    async def maybe_store_room_on_invite(self, room_id: str, room_version: RoomVersion):
+    async def maybe_store_room_on_outlier_membership(
+        self, room_id: str, room_version: RoomVersion
+    ):
         """
-        When we receive an invite over federation, store the version of the room if we
-        don't already know the room version.
+        When we receive an invite or any other event over federation that may relate to a room
+        we are not in, store the version of the room if we don't already know the room version.
         """
         await self.db_pool.simple_upsert(
-            desc="maybe_store_room_on_invite",
+            desc="maybe_store_room_on_outlier_membership",
             table="rooms",
             keyvalues={"room_id": room_id},
             values={},
diff --git a/tests/api/test_auth.py b/tests/api/test_auth.py
index 0fd55f428a..ee5217b074 100644
--- a/tests/api/test_auth.py
+++ b/tests/api/test_auth.py
@@ -282,7 +282,11 @@ class AuthTestCase(unittest.TestCase):
             )
         )
         self.store.add_access_token_to_user.assert_called_with(
-            USER_ID, token, "DEVICE", None
+            user_id=USER_ID,
+            token=token,
+            device_id="DEVICE",
+            valid_until_ms=None,
+            puppets_user_id=None,
         )
 
         def get_user(tok):
diff --git a/tests/app/test_frontend_proxy.py b/tests/app/test_frontend_proxy.py
index 4a301b84e1..40abe9d72d 100644
--- a/tests/app/test_frontend_proxy.py
+++ b/tests/app/test_frontend_proxy.py
@@ -15,6 +15,7 @@
 
 from synapse.app.generic_worker import GenericWorkerServer
 
+from tests.server import make_request
 from tests.unittest import HomeserverTestCase
 
 
@@ -55,10 +56,8 @@ class FrontendProxyTests(HomeserverTestCase):
         # Grab the resource from the site that was told to listen
         self.assertEqual(len(self.reactor.tcpServers), 1)
         site = self.reactor.tcpServers[0][1]
-        self.resource = site.resource.children[b"_matrix"].children[b"client"]
 
-        request, channel = self.make_request("PUT", "presence/a/status")
-        self.render(request)
+        _, channel = make_request(self.reactor, site, "PUT", "presence/a/status")
 
         # 400 + unrecognised, because nothing is registered
         self.assertEqual(channel.code, 400)
@@ -77,10 +76,8 @@ class FrontendProxyTests(HomeserverTestCase):
         # Grab the resource from the site that was told to listen
         self.assertEqual(len(self.reactor.tcpServers), 1)
         site = self.reactor.tcpServers[0][1]
-        self.resource = site.resource.children[b"_matrix"].children[b"client"]
 
-        request, channel = self.make_request("PUT", "presence/a/status")
-        self.render(request)
+        _, channel = make_request(self.reactor, site, "PUT", "presence/a/status")
 
         # 401, because the stub servlet still checks authentication
         self.assertEqual(channel.code, 401)
diff --git a/tests/app/test_openid_listener.py b/tests/app/test_openid_listener.py
index c2b10d2c70..ea3be95cf1 100644
--- a/tests/app/test_openid_listener.py
+++ b/tests/app/test_openid_listener.py
@@ -20,6 +20,7 @@ from synapse.app.generic_worker import GenericWorkerServer
 from synapse.app.homeserver import SynapseHomeServer
 from synapse.config.server import parse_listener_def
 
+from tests.server import make_request
 from tests.unittest import HomeserverTestCase
 
 
@@ -66,16 +67,15 @@ class FederationReaderOpenIDListenerTests(HomeserverTestCase):
         # Grab the resource from the site that was told to listen
         site = self.reactor.tcpServers[0][1]
         try:
-            self.resource = site.resource.children[b"_matrix"].children[b"federation"]
+            site.resource.children[b"_matrix"].children[b"federation"]
         except KeyError:
             if expectation == "no_resource":
                 return
             raise
 
-        request, channel = self.make_request(
-            "GET", "/_matrix/federation/v1/openid/userinfo"
+        _, channel = make_request(
+            self.reactor, site, "GET", "/_matrix/federation/v1/openid/userinfo"
         )
-        self.render(request)
 
         self.assertEqual(channel.code, 401)
 
@@ -115,15 +115,14 @@ class SynapseHomeserverOpenIDListenerTests(HomeserverTestCase):
         # Grab the resource from the site that was told to listen
         site = self.reactor.tcpServers[0][1]
         try:
-            self.resource = site.resource.children[b"_matrix"].children[b"federation"]
+            site.resource.children[b"_matrix"].children[b"federation"]
         except KeyError:
             if expectation == "no_resource":
                 return
             raise
 
-        request, channel = self.make_request(
-            "GET", "/_matrix/federation/v1/openid/userinfo"
+        _, channel = make_request(
+            self.reactor, site, "GET", "/_matrix/federation/v1/openid/userinfo"
         )
-        self.render(request)
 
         self.assertEqual(channel.code, 401)
diff --git a/tests/federation/test_complexity.py b/tests/federation/test_complexity.py
index 1471cc1a28..0187f56e21 100644
--- a/tests/federation/test_complexity.py
+++ b/tests/federation/test_complexity.py
@@ -51,7 +51,6 @@ class RoomComplexityTests(unittest.FederatingHomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/federation/unstable/rooms/%s/complexity" % (room_1,)
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         complexity = channel.json_body["v1"]
         self.assertTrue(complexity > 0, complexity)
@@ -64,7 +63,6 @@ class RoomComplexityTests(unittest.FederatingHomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/federation/unstable/rooms/%s/complexity" % (room_1,)
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         complexity = channel.json_body["v1"]
         self.assertEqual(complexity, 1.23)
diff --git a/tests/federation/test_federation_server.py b/tests/federation/test_federation_server.py
index da933ecd75..3009fbb6c4 100644
--- a/tests/federation/test_federation_server.py
+++ b/tests/federation/test_federation_server.py
@@ -51,7 +51,6 @@ class FederationServerTests(unittest.FederatingHomeserverTestCase):
             "/_matrix/federation/v1/get_missing_events/%s" % (room_1,),
             query_content,
         )
-        self.render(request)
         self.assertEquals(400, channel.code, channel.result)
         self.assertEqual(channel.json_body["errcode"], "M_NOT_JSON")
 
@@ -99,7 +98,6 @@ class StateQueryTests(unittest.FederatingHomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/federation/v1/state/%s" % (room_1,)
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
         self.assertEqual(
@@ -132,7 +130,6 @@ class StateQueryTests(unittest.FederatingHomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/federation/v1/state/%s" % (room_1,)
         )
-        self.render(request)
         self.assertEquals(403, channel.code, channel.result)
         self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
 
diff --git a/tests/federation/transport/test_server.py b/tests/federation/transport/test_server.py
index 72e22d655f..f9e3c7a51f 100644
--- a/tests/federation/transport/test_server.py
+++ b/tests/federation/transport/test_server.py
@@ -40,7 +40,6 @@ class RoomDirectoryFederationTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/federation/v1/publicRooms"
         )
-        self.render(request)
         self.assertEquals(403, channel.code)
 
     @override_config({"allow_public_rooms_over_federation": True})
@@ -48,5 +47,4 @@ class RoomDirectoryFederationTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/federation/v1/publicRooms"
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
diff --git a/tests/handlers/test_directory.py b/tests/handlers/test_directory.py
index 2ce6dc9528..ee6ef5e6fa 100644
--- a/tests/handlers/test_directory.py
+++ b/tests/handlers/test_directory.py
@@ -412,7 +412,6 @@ class TestCreateAliasACL(unittest.HomeserverTestCase):
             b"directory/room/%23test%3Atest",
             ('{"room_id":"%s"}' % (room_id,)).encode("ascii"),
         )
-        self.render(request)
         self.assertEquals(403, channel.code, channel.result)
 
     def test_allowed(self):
@@ -423,7 +422,6 @@ class TestCreateAliasACL(unittest.HomeserverTestCase):
             b"directory/room/%23unofficial_test%3Atest",
             ('{"room_id":"%s"}' % (room_id,)).encode("ascii"),
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
 
@@ -438,7 +436,6 @@ class TestRoomListSearchDisabled(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", b"directory/list/room/%s" % (room_id.encode("ascii"),), b"{}"
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
         self.room_list_handler = hs.get_room_list_handler()
@@ -452,7 +449,6 @@ class TestRoomListSearchDisabled(unittest.HomeserverTestCase):
 
         # Room list is enabled so we should get some results
         request, channel = self.make_request("GET", b"publicRooms")
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         self.assertTrue(len(channel.json_body["chunk"]) > 0)
 
@@ -461,7 +457,6 @@ class TestRoomListSearchDisabled(unittest.HomeserverTestCase):
 
         # Room list disabled so we should get no results
         request, channel = self.make_request("GET", b"publicRooms")
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         self.assertTrue(len(channel.json_body["chunk"]) == 0)
 
@@ -470,5 +465,4 @@ class TestRoomListSearchDisabled(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", b"directory/list/room/%s" % (room_id.encode("ascii"),), b"{}"
         )
-        self.render(request)
         self.assertEquals(403, channel.code, channel.result)
diff --git a/tests/handlers/test_message.py b/tests/handlers/test_message.py
index 8b57081cbe..af42775815 100644
--- a/tests/handlers/test_message.py
+++ b/tests/handlers/test_message.py
@@ -209,5 +209,4 @@ class ServerAclValidationTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", path, content={}, access_token=self.access_token
         )
-        self.render(request)
         self.assertEqual(int(channel.result["code"]), 403)
diff --git a/tests/handlers/test_oidc.py b/tests/handlers/test_oidc.py
index 0d51705849..630e6da808 100644
--- a/tests/handlers/test_oidc.py
+++ b/tests/handlers/test_oidc.py
@@ -154,6 +154,9 @@ class OidcHandlerTestCase(HomeserverTestCase):
         )
 
         self.handler = OidcHandler(hs)
+        # Mock the render error method.
+        self.render_error = Mock(return_value=None)
+        self.handler._sso_handler.render_error = self.render_error
 
         return hs
 
@@ -161,12 +164,12 @@ class OidcHandlerTestCase(HomeserverTestCase):
         return patch.dict(self.handler._provider_metadata, values)
 
     def assertRenderedError(self, error, error_description=None):
-        args = self.handler._render_error.call_args[0]
+        args = self.render_error.call_args[0]
         self.assertEqual(args[1], error)
         if error_description is not None:
             self.assertEqual(args[2], error_description)
         # Reset the render_error mock
-        self.handler._render_error.reset_mock()
+        self.render_error.reset_mock()
 
     def test_config(self):
         """Basic config correctly sets up the callback URL and client auth correctly."""
@@ -356,7 +359,6 @@ class OidcHandlerTestCase(HomeserverTestCase):
 
     def test_callback_error(self):
         """Errors from the provider returned in the callback are displayed."""
-        self.handler._render_error = Mock()
         request = Mock(args={})
         request.args[b"error"] = [b"invalid_client"]
         self.get_success(self.handler.handle_oidc_callback(request))
@@ -387,7 +389,6 @@ class OidcHandlerTestCase(HomeserverTestCase):
             "preferred_username": "bar",
         }
         user_id = "@foo:domain.org"
-        self.handler._render_error = Mock(return_value=None)
         self.handler._exchange_code = simple_async_mock(return_value=token)
         self.handler._parse_id_token = simple_async_mock(return_value=userinfo)
         self.handler._fetch_userinfo = simple_async_mock(return_value=userinfo)
@@ -435,7 +436,7 @@ class OidcHandlerTestCase(HomeserverTestCase):
             userinfo, token, user_agent, ip_address
         )
         self.handler._fetch_userinfo.assert_not_called()
-        self.handler._render_error.assert_not_called()
+        self.render_error.assert_not_called()
 
         # Handle mapping errors
         self.handler._map_userinfo_to_user = simple_async_mock(
@@ -469,7 +470,7 @@ class OidcHandlerTestCase(HomeserverTestCase):
             userinfo, token, user_agent, ip_address
         )
         self.handler._fetch_userinfo.assert_called_once_with(token)
-        self.handler._render_error.assert_not_called()
+        self.render_error.assert_not_called()
 
         # Handle userinfo fetching error
         self.handler._fetch_userinfo = simple_async_mock(raises=Exception())
@@ -485,7 +486,6 @@ class OidcHandlerTestCase(HomeserverTestCase):
 
     def test_callback_session(self):
         """The callback verifies the session presence and validity"""
-        self.handler._render_error = Mock(return_value=None)
         request = Mock(spec=["args", "getCookie", "addCookie"])
 
         # Missing cookie
diff --git a/tests/handlers/test_sync.py b/tests/handlers/test_sync.py
index e178d7765b..e62586142e 100644
--- a/tests/handlers/test_sync.py
+++ b/tests/handlers/test_sync.py
@@ -16,7 +16,7 @@
 from synapse.api.errors import Codes, ResourceLimitError
 from synapse.api.filtering import DEFAULT_FILTER_COLLECTION
 from synapse.handlers.sync import SyncConfig
-from synapse.types import UserID
+from synapse.types import UserID, create_requester
 
 import tests.unittest
 import tests.utils
@@ -38,6 +38,7 @@ class SyncTestCase(tests.unittest.HomeserverTestCase):
         user_id1 = "@user1:test"
         user_id2 = "@user2:test"
         sync_config = self._generate_sync_config(user_id1)
+        requester = create_requester(user_id1)
 
         self.reactor.advance(100)  # So we get not 0 time
         self.auth_blocking._limit_usage_by_mau = True
@@ -45,21 +46,26 @@ class SyncTestCase(tests.unittest.HomeserverTestCase):
 
         # Check that the happy case does not throw errors
         self.get_success(self.store.upsert_monthly_active_user(user_id1))
-        self.get_success(self.sync_handler.wait_for_sync_for_user(sync_config))
+        self.get_success(
+            self.sync_handler.wait_for_sync_for_user(requester, sync_config)
+        )
 
         # Test that global lock works
         self.auth_blocking._hs_disabled = True
         e = self.get_failure(
-            self.sync_handler.wait_for_sync_for_user(sync_config), ResourceLimitError
+            self.sync_handler.wait_for_sync_for_user(requester, sync_config),
+            ResourceLimitError,
         )
         self.assertEquals(e.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
 
         self.auth_blocking._hs_disabled = False
 
         sync_config = self._generate_sync_config(user_id2)
+        requester = create_requester(user_id2)
 
         e = self.get_failure(
-            self.sync_handler.wait_for_sync_for_user(sync_config), ResourceLimitError
+            self.sync_handler.wait_for_sync_for_user(requester, sync_config),
+            ResourceLimitError,
         )
         self.assertEquals(e.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
 
diff --git a/tests/handlers/test_typing.py b/tests/handlers/test_typing.py
index 16ff2e22d2..abbdf2d524 100644
--- a/tests/handlers/test_typing.py
+++ b/tests/handlers/test_typing.py
@@ -228,7 +228,6 @@ class TypingNotificationsTestCase(unittest.HomeserverTestCase):
             ),
             federation_auth_origin=b"farm",
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         self.on_new_event.assert_has_calls([call("typing_key", 1, rooms=[ROOM_ID])])
diff --git a/tests/handlers/test_user_directory.py b/tests/handlers/test_user_directory.py
index 87be94111f..98e5af2072 100644
--- a/tests/handlers/test_user_directory.py
+++ b/tests/handlers/test_user_directory.py
@@ -537,7 +537,6 @@ class TestUserDirSearchDisabled(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", b"user_directory/search", b'{"search_term":"user2"}'
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         self.assertTrue(len(channel.json_body["results"]) > 0)
 
@@ -546,6 +545,5 @@ class TestUserDirSearchDisabled(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", b"user_directory/search", b'{"search_term":"user2"}'
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         self.assertTrue(len(channel.json_body["results"]) == 0)
diff --git a/tests/http/test_additional_resource.py b/tests/http/test_additional_resource.py
index 62d36c2906..05e9c449be 100644
--- a/tests/http/test_additional_resource.py
+++ b/tests/http/test_additional_resource.py
@@ -17,6 +17,7 @@
 from synapse.http.additional_resource import AdditionalResource
 from synapse.http.server import respond_with_json
 
+from tests.server import FakeSite, make_request
 from tests.unittest import HomeserverTestCase
 
 
@@ -43,20 +44,18 @@ class AdditionalResourceTests(HomeserverTestCase):
 
     def test_async(self):
         handler = _AsyncTestCustomEndpoint({}, None).handle_request
-        self.resource = AdditionalResource(self.hs, handler)
+        resource = AdditionalResource(self.hs, handler)
 
-        request, channel = self.make_request("GET", "/")
-        self.render(request)
+        request, channel = make_request(self.reactor, FakeSite(resource), "GET", "/")
 
         self.assertEqual(request.code, 200)
         self.assertEqual(channel.json_body, {"some_key": "some_value_async"})
 
     def test_sync(self):
         handler = _SyncTestCustomEndpoint({}, None).handle_request
-        self.resource = AdditionalResource(self.hs, handler)
+        resource = AdditionalResource(self.hs, handler)
 
-        request, channel = self.make_request("GET", "/")
-        self.render(request)
+        request, channel = make_request(self.reactor, FakeSite(resource), "GET", "/")
 
         self.assertEqual(request.code, 200)
         self.assertEqual(channel.json_body, {"some_key": "some_value_sync"})
diff --git a/tests/module_api/test_api.py b/tests/module_api/test_api.py
index 9b573ac24d..27206ca3db 100644
--- a/tests/module_api/test_api.py
+++ b/tests/module_api/test_api.py
@@ -94,12 +94,13 @@ class ModuleApiTestCase(HomeserverTestCase):
         self.assertFalse(hasattr(event, "state_key"))
         self.assertDictEqual(event.content, content)
 
+        expected_requester = create_requester(
+            user_id, authenticated_entity=self.hs.hostname
+        )
+
         # Check that the event was sent
         self.event_creation_handler.create_and_send_nonmember_event.assert_called_with(
-            create_requester(user_id),
-            event_dict,
-            ratelimit=False,
-            ignore_shadow_ban=True,
+            expected_requester, event_dict, ratelimit=False, ignore_shadow_ban=True,
         )
 
         # Create and send a state event
@@ -128,7 +129,7 @@ class ModuleApiTestCase(HomeserverTestCase):
 
         # Check that the event was sent
         self.event_creation_handler.create_and_send_nonmember_event.assert_called_with(
-            create_requester(user_id),
+            expected_requester,
             {
                 "type": "m.room.power_levels",
                 "content": content,
diff --git a/tests/replication/_base.py b/tests/replication/_base.py
index 5c633ac6df..516db4c30a 100644
--- a/tests/replication/_base.py
+++ b/tests/replication/_base.py
@@ -36,7 +36,7 @@ from synapse.server import HomeServer
 from synapse.util import Clock
 
 from tests import unittest
-from tests.server import FakeTransport, render
+from tests.server import FakeTransport
 
 try:
     import hiredis
@@ -240,8 +240,8 @@ class BaseMultiWorkerStreamTestCase(unittest.HomeserverTestCase):
             lambda: self._handle_http_replication_attempt(self.hs, 8765),
         )
 
-    def create_test_json_resource(self):
-        """Overrides `HomeserverTestCase.create_test_json_resource`.
+    def create_test_resource(self):
+        """Overrides `HomeserverTestCase.create_test_resource`.
         """
         # We override this so that it automatically registers all the HTTP
         # replication servlets, without having to explicitly do that in all
@@ -347,9 +347,6 @@ class BaseMultiWorkerStreamTestCase(unittest.HomeserverTestCase):
         config["worker_replication_http_port"] = "8765"
         return config
 
-    def render_on_worker(self, worker_hs: HomeServer, request: SynapseRequest):
-        render(request, self._hs_to_site[worker_hs].resource, self.reactor)
-
     def replicate(self):
         """Tell the master side of replication that something has happened, and then
         wait for the replication to occur.
diff --git a/tests/replication/test_client_reader_shard.py b/tests/replication/test_client_reader_shard.py
index 86c03fd89c..96801db473 100644
--- a/tests/replication/test_client_reader_shard.py
+++ b/tests/replication/test_client_reader_shard.py
@@ -20,7 +20,7 @@ from synapse.rest.client.v2_alpha import register
 
 from tests.replication._base import BaseMultiWorkerStreamTestCase
 from tests.rest.client.v2_alpha.test_auth import DummyRecaptchaChecker
-from tests.server import FakeChannel
+from tests.server import FakeChannel, make_request
 
 logger = logging.getLogger(__name__)
 
@@ -46,23 +46,28 @@ class ClientReaderTestCase(BaseMultiWorkerStreamTestCase):
         """Test that registration works when using a single client reader worker.
         """
         worker_hs = self.make_worker_hs("synapse.app.client_reader")
+        site = self._hs_to_site[worker_hs]
 
-        request_1, channel_1 = self.make_request(
+        request_1, channel_1 = make_request(
+            self.reactor,
+            site,
             "POST",
             "register",
             {"username": "user", "type": "m.login.password", "password": "bar"},
         )  # type: SynapseRequest, FakeChannel
-        self.render_on_worker(worker_hs, request_1)
         self.assertEqual(request_1.code, 401)
 
         # Grab the session
         session = channel_1.json_body["session"]
 
         # also complete the dummy auth
-        request_2, channel_2 = self.make_request(
-            "POST", "register", {"auth": {"session": session, "type": "m.login.dummy"}}
+        request_2, channel_2 = make_request(
+            self.reactor,
+            site,
+            "POST",
+            "register",
+            {"auth": {"session": session, "type": "m.login.dummy"}},
         )  # type: SynapseRequest, FakeChannel
-        self.render_on_worker(worker_hs, request_2)
         self.assertEqual(request_2.code, 200)
 
         # We're given a registered user.
@@ -74,22 +79,28 @@ class ClientReaderTestCase(BaseMultiWorkerStreamTestCase):
         worker_hs_1 = self.make_worker_hs("synapse.app.client_reader")
         worker_hs_2 = self.make_worker_hs("synapse.app.client_reader")
 
-        request_1, channel_1 = self.make_request(
+        site_1 = self._hs_to_site[worker_hs_1]
+        request_1, channel_1 = make_request(
+            self.reactor,
+            site_1,
             "POST",
             "register",
             {"username": "user", "type": "m.login.password", "password": "bar"},
         )  # type: SynapseRequest, FakeChannel
-        self.render_on_worker(worker_hs_1, request_1)
         self.assertEqual(request_1.code, 401)
 
         # Grab the session
         session = channel_1.json_body["session"]
 
         # also complete the dummy auth
-        request_2, channel_2 = self.make_request(
-            "POST", "register", {"auth": {"session": session, "type": "m.login.dummy"}}
+        site_2 = self._hs_to_site[worker_hs_2]
+        request_2, channel_2 = make_request(
+            self.reactor,
+            site_2,
+            "POST",
+            "register",
+            {"auth": {"session": session, "type": "m.login.dummy"}},
         )  # type: SynapseRequest, FakeChannel
-        self.render_on_worker(worker_hs_2, request_2)
         self.assertEqual(request_2.code, 200)
 
         # We're given a registered user.
diff --git a/tests/replication/test_multi_media_repo.py b/tests/replication/test_multi_media_repo.py
index 77c261dbf7..48b574ccbe 100644
--- a/tests/replication/test_multi_media_repo.py
+++ b/tests/replication/test_multi_media_repo.py
@@ -28,7 +28,7 @@ from synapse.server import HomeServer
 
 from tests.http import TestServerTLSConnectionFactory, get_test_ca_cert_file
 from tests.replication._base import BaseMultiWorkerStreamTestCase
-from tests.server import FakeChannel, FakeTransport
+from tests.server import FakeChannel, FakeSite, FakeTransport, make_request
 
 logger = logging.getLogger(__name__)
 
@@ -67,14 +67,16 @@ class MediaRepoShardTestCase(BaseMultiWorkerStreamTestCase):
             The channel for the *client* request and the *outbound* request for
             the media which the caller should respond to.
         """
-
-        request, channel = self.make_request(
+        resource = hs.get_media_repository_resource().children[b"download"]
+        _, channel = make_request(
+            self.reactor,
+            FakeSite(resource),
             "GET",
             "/{}/{}".format(target, media_id),
             shorthand=False,
             access_token=self.access_token,
+            await_result=False,
         )
-        request.render(hs.get_media_repository_resource().children[b"download"])
         self.pump()
 
         clients = self.reactor.tcpClients
diff --git a/tests/replication/test_sharded_event_persister.py b/tests/replication/test_sharded_event_persister.py
index 82cf033d4e..77fc3856d5 100644
--- a/tests/replication/test_sharded_event_persister.py
+++ b/tests/replication/test_sharded_event_persister.py
@@ -22,6 +22,7 @@ from synapse.rest.client.v1 import login, room
 from synapse.rest.client.v2_alpha import sync
 
 from tests.replication._base import BaseMultiWorkerStreamTestCase
+from tests.server import make_request
 from tests.utils import USE_POSTGRES_FOR_TESTS
 
 logger = logging.getLogger(__name__)
@@ -148,6 +149,7 @@ class EventPersisterShardTestCase(BaseMultiWorkerStreamTestCase):
         sync_hs = self.make_worker_hs(
             "synapse.app.generic_worker", {"worker_name": "sync"},
         )
+        sync_hs_site = self._hs_to_site[sync_hs]
 
         # Specially selected room IDs that get persisted on different workers.
         room_id1 = "!foo:test"
@@ -178,8 +180,9 @@ class EventPersisterShardTestCase(BaseMultiWorkerStreamTestCase):
         )
 
         # Do an initial sync so that we're up to date.
-        request, channel = self.make_request("GET", "/sync", access_token=access_token)
-        self.render_on_worker(sync_hs, request)
+        request, channel = make_request(
+            self.reactor, sync_hs_site, "GET", "/sync", access_token=access_token
+        )
         next_batch = channel.json_body["next_batch"]
 
         # We now gut wrench into the events stream MultiWriterIdGenerator on
@@ -203,10 +206,13 @@ class EventPersisterShardTestCase(BaseMultiWorkerStreamTestCase):
 
         # Check that syncing still gets the new event, despite the gap in the
         # stream IDs.
-        request, channel = self.make_request(
-            "GET", "/sync?since={}".format(next_batch), access_token=access_token
+        request, channel = make_request(
+            self.reactor,
+            sync_hs_site,
+            "GET",
+            "/sync?since={}".format(next_batch),
+            access_token=access_token,
         )
-        self.render_on_worker(sync_hs, request)
 
         # We should only see the new event and nothing else
         self.assertIn(room_id1, channel.json_body["rooms"]["join"])
@@ -230,12 +236,13 @@ class EventPersisterShardTestCase(BaseMultiWorkerStreamTestCase):
         response = self.helper.send(room_id2, body="Hi!", tok=self.other_access_token)
         first_event_in_room2 = response["event_id"]
 
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            sync_hs_site,
             "GET",
             "/sync?since={}".format(vector_clock_token),
             access_token=access_token,
         )
-        self.render_on_worker(sync_hs, request)
 
         self.assertNotIn(room_id1, channel.json_body["rooms"]["join"])
         self.assertIn(room_id2, channel.json_body["rooms"]["join"])
@@ -254,10 +261,13 @@ class EventPersisterShardTestCase(BaseMultiWorkerStreamTestCase):
         self.helper.send(room_id1, body="Hi again!", tok=self.other_access_token)
         self.helper.send(room_id2, body="Hi again!", tok=self.other_access_token)
 
-        request, channel = self.make_request(
-            "GET", "/sync?since={}".format(next_batch), access_token=access_token
+        request, channel = make_request(
+            self.reactor,
+            sync_hs_site,
+            "GET",
+            "/sync?since={}".format(next_batch),
+            access_token=access_token,
         )
-        self.render_on_worker(sync_hs, request)
 
         prev_batch1 = channel.json_body["rooms"]["join"][room_id1]["timeline"][
             "prev_batch"
@@ -269,50 +279,54 @@ class EventPersisterShardTestCase(BaseMultiWorkerStreamTestCase):
         # Paginating back in the first room should not produce any results, as
         # no events have happened in it. This tests that we are correctly
         # filtering results based on the vector clock portion.
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            sync_hs_site,
             "GET",
             "/rooms/{}/messages?from={}&to={}&dir=b".format(
                 room_id1, prev_batch1, vector_clock_token
             ),
             access_token=access_token,
         )
-        self.render_on_worker(sync_hs, request)
         self.assertListEqual([], channel.json_body["chunk"])
 
         # Paginating back on the second room should produce the first event
         # again. This tests that pagination isn't completely broken.
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            sync_hs_site,
             "GET",
             "/rooms/{}/messages?from={}&to={}&dir=b".format(
                 room_id2, prev_batch2, vector_clock_token
             ),
             access_token=access_token,
         )
-        self.render_on_worker(sync_hs, request)
         self.assertEqual(len(channel.json_body["chunk"]), 1)
         self.assertEqual(
             channel.json_body["chunk"][0]["event_id"], first_event_in_room2
         )
 
         # Paginating forwards should give the same results
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            sync_hs_site,
             "GET",
             "/rooms/{}/messages?from={}&to={}&dir=f".format(
                 room_id1, vector_clock_token, prev_batch1
             ),
             access_token=access_token,
         )
-        self.render_on_worker(sync_hs, request)
         self.assertListEqual([], channel.json_body["chunk"])
 
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            sync_hs_site,
             "GET",
             "/rooms/{}/messages?from={}&to={}&dir=f".format(
                 room_id2, vector_clock_token, prev_batch2,
             ),
             access_token=access_token,
         )
-        self.render_on_worker(sync_hs, request)
         self.assertEqual(len(channel.json_body["chunk"]), 1)
         self.assertEqual(
             channel.json_body["chunk"][0]["event_id"], first_event_in_room2
diff --git a/tests/rest/admin/test_admin.py b/tests/rest/admin/test_admin.py
index 0f1144fe1e..898e43411e 100644
--- a/tests/rest/admin/test_admin.py
+++ b/tests/rest/admin/test_admin.py
@@ -30,19 +30,19 @@ from synapse.rest.client.v1 import login, room
 from synapse.rest.client.v2_alpha import groups
 
 from tests import unittest
+from tests.server import FakeSite, make_request
 
 
 class VersionTestCase(unittest.HomeserverTestCase):
     url = "/_synapse/admin/v1/server_version"
 
-    def create_test_json_resource(self):
+    def create_test_resource(self):
         resource = JsonResource(self.hs)
         VersionServlet(self.hs).register(resource)
         return resource
 
     def test_version_string(self):
         request, channel = self.make_request("GET", self.url, shorthand=False)
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(
@@ -75,7 +75,6 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
             content={"localpart": "test"},
         )
 
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         group_id = channel.json_body["group_id"]
@@ -88,14 +87,12 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url.encode("ascii"), access_token=self.admin_user_tok, content={}
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         url = "/groups/%s/self/accept_invite" % (group_id,)
         request, channel = self.make_request(
             "PUT", url.encode("ascii"), access_token=self.other_user_token, content={}
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Check other user knows they're in the group
@@ -111,7 +108,6 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
             content={"localpart": "test"},
         )
 
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Check group returns 404
@@ -131,7 +127,6 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
             "GET", url.encode("ascii"), access_token=self.admin_user_tok
         )
 
-        self.render(request)
         self.assertEqual(
             expect_code, int(channel.result["code"]), msg=channel.result["body"]
         )
@@ -143,7 +138,6 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
             "GET", "/joined_groups".encode("ascii"), access_token=access_token
         )
 
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         return channel.json_body["groups"]
@@ -222,11 +216,14 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
 
     def _ensure_quarantined(self, admin_user_tok, server_and_media_id):
         """Ensure a piece of media is quarantined when trying to access it."""
-        request, channel = self.make_request(
-            "GET", server_and_media_id, shorthand=False, access_token=admin_user_tok,
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(self.download_resource),
+            "GET",
+            server_and_media_id,
+            shorthand=False,
+            access_token=admin_user_tok,
         )
-        request.render(self.download_resource)
-        self.pump(1.0)
 
         # Should be quarantined
         self.assertEqual(
@@ -247,7 +244,6 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url.encode("ascii"), access_token=non_admin_user_tok,
         )
-        self.render(request)
 
         # Expect a forbidden error
         self.assertEqual(
@@ -261,7 +257,6 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url.encode("ascii"), access_token=non_admin_user_tok,
         )
-        self.render(request)
 
         # Expect a forbidden error
         self.assertEqual(
@@ -287,14 +282,14 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
         server_name, media_id = server_name_and_media_id.split("/")
 
         # Attempt to access the media
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(self.download_resource),
             "GET",
             server_name_and_media_id,
             shorthand=False,
             access_token=non_admin_user_tok,
         )
-        request.render(self.download_resource)
-        self.pump(1.0)
 
         # Should be successful
         self.assertEqual(200, int(channel.code), msg=channel.result["body"])
@@ -305,7 +300,6 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
             urllib.parse.quote(media_id),
         )
         request, channel = self.make_request("POST", url, access_token=admin_user_tok,)
-        self.render(request)
         self.pump(1.0)
         self.assertEqual(200, int(channel.code), msg=channel.result["body"])
 
@@ -358,7 +352,6 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
                 room_id
             )
         request, channel = self.make_request("POST", url, access_token=admin_user_tok,)
-        self.render(request)
         self.pump(1.0)
         self.assertEqual(200, int(channel.code), msg=channel.result["body"])
         self.assertEqual(
@@ -405,7 +398,6 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url.encode("ascii"), access_token=admin_user_tok,
         )
-        self.render(request)
         self.pump(1.0)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(
@@ -448,7 +440,6 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url.encode("ascii"), access_token=admin_user_tok,
         )
-        self.render(request)
         self.pump(1.0)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(
@@ -462,14 +453,14 @@ class QuarantineMediaTestCase(unittest.HomeserverTestCase):
         self._ensure_quarantined(admin_user_tok, server_and_media_id_1)
 
         # Attempt to access each piece of media
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(self.download_resource),
             "GET",
             server_and_media_id_2,
             shorthand=False,
             access_token=non_admin_user_tok,
         )
-        request.render(self.download_resource)
-        self.pump(1.0)
 
         # Shouldn't be quarantined
         self.assertEqual(
diff --git a/tests/rest/admin/test_device.py b/tests/rest/admin/test_device.py
index d89eb90cfe..cf3a007598 100644
--- a/tests/rest/admin/test_device.py
+++ b/tests/rest/admin/test_device.py
@@ -51,19 +51,16 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         Try to get a device of an user without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
 
         request, channel = self.make_request("PUT", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
 
         request, channel = self.make_request("DELETE", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -75,7 +72,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -83,7 +79,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", self.url, access_token=self.other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -91,7 +86,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", self.url, access_token=self.other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -108,7 +102,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -116,7 +109,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -124,7 +116,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -141,7 +132,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only lookup local users", channel.json_body["error"])
@@ -149,7 +139,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only lookup local users", channel.json_body["error"])
@@ -157,7 +146,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only lookup local users", channel.json_body["error"])
@@ -173,7 +161,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -181,14 +168,12 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
         request, channel = self.make_request(
             "DELETE", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         # Delete unknown device returns status 200
         self.assertEqual(200, channel.code, msg=channel.json_body)
@@ -218,7 +203,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.TOO_LARGE, channel.json_body["errcode"])
@@ -227,7 +211,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual("new display", channel.json_body["display_name"])
@@ -247,7 +230,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
@@ -255,7 +237,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual("new display", channel.json_body["display_name"])
@@ -272,7 +253,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
@@ -280,7 +260,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual("new displayname", channel.json_body["display_name"])
@@ -292,7 +271,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(self.other_user, channel.json_body["user_id"])
@@ -316,7 +294,6 @@ class DeviceRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
@@ -347,7 +324,6 @@ class DevicesRestTestCase(unittest.HomeserverTestCase):
         Try to list devices of an user without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -361,7 +337,6 @@ class DevicesRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -374,7 +349,6 @@ class DevicesRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -388,7 +362,6 @@ class DevicesRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only lookup local users", channel.json_body["error"])
@@ -403,7 +376,6 @@ class DevicesRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
@@ -422,7 +394,6 @@ class DevicesRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(number_devices, channel.json_body["total"])
@@ -461,7 +432,6 @@ class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):
         Try to delete devices of an user without authentication.
         """
         request, channel = self.make_request("POST", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -475,7 +445,6 @@ class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", self.url, access_token=other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -488,7 +457,6 @@ class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -502,7 +470,6 @@ class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only lookup local users", channel.json_body["error"])
@@ -518,7 +485,6 @@ class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         # Delete unknown devices returns status 200
         self.assertEqual(200, channel.code, msg=channel.json_body)
@@ -550,7 +516,6 @@ class DeleteDevicesRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
diff --git a/tests/rest/admin/test_event_reports.py b/tests/rest/admin/test_event_reports.py
index 303622217f..11b72c10f7 100644
--- a/tests/rest/admin/test_event_reports.py
+++ b/tests/rest/admin/test_event_reports.py
@@ -75,7 +75,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         Try to get an event report without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -88,7 +87,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.other_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -101,7 +99,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -117,7 +114,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -133,7 +129,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -149,7 +144,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=5&limit=10", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -167,7 +161,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
             self.url + "?room_id=%s" % self.room_id1,
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 10)
@@ -188,7 +181,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
             self.url + "?user_id=%s" % self.other_user,
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 10)
@@ -209,7 +201,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
             self.url + "?user_id=%s&room_id=%s" % (self.other_user, self.room_id1),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 5)
@@ -230,7 +221,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?dir=b", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -247,7 +237,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?dir=f", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -268,7 +257,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?dir=bar", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -282,7 +270,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=-5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -295,7 +282,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=-5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -310,7 +296,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=20", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -322,7 +307,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=21", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -334,7 +318,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=19", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -347,7 +330,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=19", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -366,7 +348,6 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
             json.dumps({"score": -100, "reason": "this makes me sad"}),
             access_token=user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
     def _check_fields(self, content):
@@ -419,7 +400,6 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
         Try to get event report without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -432,7 +412,6 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.other_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -445,7 +424,6 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self._check_fields(channel.json_body)
@@ -461,7 +439,6 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
             "/_synapse/admin/v1/event_reports/-123",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -476,7 +453,6 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
             "/_synapse/admin/v1/event_reports/abcdef",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -491,7 +467,6 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
             "/_synapse/admin/v1/event_reports/",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -510,7 +485,6 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
             "/_synapse/admin/v1/event_reports/123",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -528,7 +502,6 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
             json.dumps({"score": -100, "reason": "this makes me sad"}),
             access_token=user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
     def _check_fields(self, content):
diff --git a/tests/rest/admin/test_media.py b/tests/rest/admin/test_media.py
index 721fa1ed51..2a65ab33bd 100644
--- a/tests/rest/admin/test_media.py
+++ b/tests/rest/admin/test_media.py
@@ -23,6 +23,7 @@ from synapse.rest.client.v1 import login, profile, room
 from synapse.rest.media.v1.filepath import MediaFilePaths
 
 from tests import unittest
+from tests.server import FakeSite, make_request
 
 
 class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
@@ -50,7 +51,6 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
         url = "/_synapse/admin/v1/media/%s/%s" % (self.server_name, "12345")
 
         request, channel = self.make_request("DELETE", url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -67,7 +67,6 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", url, access_token=self.other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -81,7 +80,6 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -95,7 +93,6 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only delete local media", channel.json_body["error"])
@@ -124,14 +121,14 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
         self.assertEqual(server_name, self.server_name)
 
         # Attempt to access media
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(download_resource),
             "GET",
             server_and_media_id,
             shorthand=False,
             access_token=self.admin_user_tok,
         )
-        request.render(download_resource)
-        self.pump(1.0)
 
         # Should be successful
         self.assertEqual(
@@ -152,7 +149,6 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(1, channel.json_body["total"])
@@ -161,14 +157,14 @@ class DeleteMediaByIDTestCase(unittest.HomeserverTestCase):
         )
 
         # Attempt to access media
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(download_resource),
             "GET",
             server_and_media_id,
             shorthand=False,
             access_token=self.admin_user_tok,
         )
-        request.render(download_resource)
-        self.pump(1.0)
         self.assertEqual(
             404,
             channel.code,
@@ -210,7 +206,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
         """
 
         request, channel = self.make_request("POST", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -225,7 +220,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", self.url, access_token=self.other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -239,7 +233,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url + "?before_ts=1234", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only delete local media", channel.json_body["error"])
@@ -251,7 +244,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_PARAM, channel.json_body["errcode"])
@@ -266,7 +258,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", self.url + "?before_ts=-1234", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -280,7 +271,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=1234&size_gt=-1234",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -294,7 +284,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=1234&keep_profiles=not_bool",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.UNKNOWN, channel.json_body["errcode"])
@@ -325,7 +314,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(1, channel.json_body["total"])
         self.assertEqual(
@@ -350,7 +338,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
 
@@ -363,7 +350,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(1, channel.json_body["total"])
         self.assertEqual(
@@ -387,7 +373,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms) + "&size_gt=67",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
 
@@ -399,7 +384,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms) + "&size_gt=66",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(1, channel.json_body["total"])
         self.assertEqual(
@@ -424,7 +408,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             content=json.dumps({"avatar_url": "mxc://%s" % (server_and_media_id,)}),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
         now_ms = self.clock.time_msec()
@@ -433,7 +416,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=true",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
 
@@ -445,7 +427,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=false",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(1, channel.json_body["total"])
         self.assertEqual(
@@ -471,7 +452,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             content=json.dumps({"url": "mxc://%s" % (server_and_media_id,)}),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
         now_ms = self.clock.time_msec()
@@ -480,7 +460,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=true",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
 
@@ -492,7 +471,6 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
             self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=false",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(1, channel.json_body["total"])
         self.assertEqual(
@@ -535,14 +513,14 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
         media_id = server_and_media_id.split("/")[1]
         local_path = self.filepaths.local_media_filepath(media_id)
 
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(download_resource),
             "GET",
             server_and_media_id,
             shorthand=False,
             access_token=self.admin_user_tok,
         )
-        request.render(download_resource)
-        self.pump(1.0)
 
         if expect_success:
             self.assertEqual(
diff --git a/tests/rest/admin/test_room.py b/tests/rest/admin/test_room.py
index 535d68f284..54824a5410 100644
--- a/tests/rest/admin/test_room.py
+++ b/tests/rest/admin/test_room.py
@@ -85,7 +85,6 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
             json.dumps({"new_room_user_id": self.admin_user}),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
@@ -110,7 +109,6 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
             json.dumps({"history_visibility": "world_readable"}),
             access_token=self.other_user_token,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Test that the admin can still send shutdown
@@ -121,7 +119,6 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
             json.dumps({"new_room_user_id": self.admin_user}),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
@@ -136,7 +133,6 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok
         )
-        self.render(request)
         self.assertEqual(
             expect_code, int(channel.result["code"]), msg=channel.result["body"]
         )
@@ -145,7 +141,6 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok
         )
-        self.render(request)
         self.assertEqual(
             expect_code, int(channel.result["code"]), msg=channel.result["body"]
         )
@@ -192,7 +187,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", self.url, json.dumps({}), access_token=self.other_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -206,7 +200,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url, json.dumps({}), access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -220,7 +213,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url, json.dumps({}), access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(
@@ -239,7 +231,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertIn("new_room_id", channel.json_body)
@@ -259,7 +250,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(
@@ -278,7 +268,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.BAD_JSON, channel.json_body["errcode"])
@@ -295,7 +284,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.BAD_JSON, channel.json_body["errcode"])
@@ -322,7 +310,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(None, channel.json_body["new_room_id"])
@@ -356,7 +343,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(None, channel.json_body["new_room_id"])
@@ -391,7 +377,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(None, channel.json_body["new_room_id"])
@@ -439,7 +424,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             json.dumps({"new_room_user_id": self.admin_user}),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(self.other_user, channel.json_body["kicked_users"][0])
@@ -470,7 +454,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             json.dumps({"history_visibility": "world_readable"}),
             access_token=self.other_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Test that room is not purged
@@ -488,7 +471,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
             json.dumps({"new_room_user_id": self.admin_user}),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(self.other_user, channel.json_body["kicked_users"][0])
@@ -551,7 +533,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok
         )
-        self.render(request)
         self.assertEqual(
             expect_code, int(channel.result["code"]), msg=channel.result["body"]
         )
@@ -560,7 +541,6 @@ class DeleteRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok
         )
-        self.render(request)
         self.assertEqual(
             expect_code, int(channel.result["code"]), msg=channel.result["body"]
         )
@@ -595,7 +575,6 @@ class PurgeRoomTestCase(unittest.HomeserverTestCase):
             {"room_id": room_id},
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
@@ -647,7 +626,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         # Check request completed successfully
         self.assertEqual(200, int(channel.code), msg=channel.json_body)
@@ -729,7 +707,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
             request, channel = self.make_request(
                 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
             )
-            self.render(request)
             self.assertEqual(
                 200, int(channel.result["code"]), msg=channel.result["body"]
             )
@@ -770,7 +747,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
     def test_correct_room_attributes(self):
@@ -794,7 +770,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
             {"room_id": room_id},
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Set this new alias as the canonical alias for this room
@@ -822,7 +797,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Check that rooms were returned
@@ -867,7 +841,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
                 {"room_id": room_id},
                 access_token=admin_user_tok,
             )
-            self.render(request)
             self.assertEqual(
                 200, int(channel.result["code"]), msg=channel.result["body"]
             )
@@ -905,7 +878,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
             request, channel = self.make_request(
                 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
             )
-            self.render(request)
             self.assertEqual(200, channel.code, msg=channel.json_body)
 
             # Check that rooms were returned
@@ -1042,7 +1014,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
             request, channel = self.make_request(
                 "GET", url.encode("ascii"), access_token=self.admin_user_tok,
             )
-            self.render(request)
             self.assertEqual(expected_http_code, channel.code, msg=channel.json_body)
 
             if expected_http_code != 200:
@@ -1104,7 +1075,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
         self.assertIn("room_id", channel.json_body)
@@ -1152,7 +1122,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
         self.assertCountEqual(
@@ -1164,7 +1133,6 @@ class RoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
 
         self.assertCountEqual(
@@ -1208,7 +1176,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.second_tok,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -1225,7 +1192,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_PARAM, channel.json_body["errcode"])
@@ -1242,7 +1208,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -1259,7 +1224,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(
@@ -1280,7 +1244,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("No known servers", channel.json_body["error"])
@@ -1298,7 +1261,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(
@@ -1318,7 +1280,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(self.public_room_id, channel.json_body["room_id"])
@@ -1328,7 +1289,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,
         )
-        self.render(request)
         self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(self.public_room_id, channel.json_body["joined_rooms"][0])
 
@@ -1349,7 +1309,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -1377,7 +1336,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/joined_rooms", access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])
 
@@ -1392,7 +1350,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(private_room_id, channel.json_body["room_id"])
 
@@ -1401,7 +1358,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,
         )
-        self.render(request)
         self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])
 
@@ -1422,7 +1378,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
             content=body.encode(encoding="utf_8"),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(private_room_id, channel.json_body["room_id"])
@@ -1432,7 +1387,6 @@ class JoinAliasRoomTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/joined_rooms", access_token=self.second_tok,
         )
-        self.render(request)
         self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(private_room_id, channel.json_body["joined_rooms"][0])
 
diff --git a/tests/rest/admin/test_statistics.py b/tests/rest/admin/test_statistics.py
index 816683a612..907b49f889 100644
--- a/tests/rest/admin/test_statistics.py
+++ b/tests/rest/admin/test_statistics.py
@@ -47,7 +47,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         Try to list users without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -59,7 +58,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, json.dumps({}), access_token=self.other_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -72,7 +70,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?order_by=bar", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -81,7 +78,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=-5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -90,7 +86,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=-5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -99,7 +94,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from_ts=-1234", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -108,7 +102,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?until_ts=-1234", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -119,7 +112,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
             self.url + "?from_ts=10&until_ts=5",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -128,7 +120,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?search_term=", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -137,7 +128,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?dir=bar", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -151,7 +141,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 10)
@@ -168,7 +157,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -185,7 +173,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=5&limit=10", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
@@ -206,7 +193,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=20", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_users)
@@ -218,7 +204,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=21", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_users)
@@ -230,7 +215,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=19", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_users)
@@ -242,7 +226,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=19", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_users)
@@ -258,7 +241,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
@@ -337,7 +319,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["users"][0]["media_count"], 3)
 
@@ -346,7 +327,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from_ts=%s" % (ts1,), access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 0)
 
@@ -362,7 +342,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
             self.url + "?from_ts=%s&until_ts=%s" % (ts1, ts2),
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["users"][0]["media_count"], 3)
 
@@ -370,7 +349,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?until_ts=%s" % (ts2,), access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["users"][0]["media_count"], 6)
 
@@ -381,7 +359,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 20)
 
@@ -391,7 +368,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
             self.url + "?search_term=foo_user_1",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 11)
 
@@ -401,7 +377,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
             self.url + "?search_term=bar_user_10",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["users"][0]["displayname"], "bar_user_10")
         self.assertEqual(channel.json_body["total"], 1)
@@ -410,7 +385,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?search_term=foobar", access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], 0)
 
@@ -476,7 +450,6 @@ class UserMediaStatisticsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url.encode("ascii"), access_token=self.admin_user_tok,
         )
-        self.render(request)
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(channel.json_body["total"], len(expected_user_list))
 
diff --git a/tests/rest/admin/test_user.py b/tests/rest/admin/test_user.py
index d74efede06..cc8a70be04 100644
--- a/tests/rest/admin/test_user.py
+++ b/tests/rest/admin/test_user.py
@@ -24,8 +24,8 @@ from mock import Mock
 import synapse.rest.admin
 from synapse.api.constants import UserTypes
 from synapse.api.errors import Codes, HttpResponseException, ResourceLimitError
-from synapse.rest.client.v1 import login, profile, room
-from synapse.rest.client.v2_alpha import sync
+from synapse.rest.client.v1 import login, logout, profile, room
+from synapse.rest.client.v2_alpha import devices, sync
 
 from tests import unittest
 from tests.test_utils import make_awaitable
@@ -71,7 +71,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         self.hs.config.registration_shared_secret = None
 
         request, channel = self.make_request("POST", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(
@@ -89,7 +88,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         self.hs.get_secrets = Mock(return_value=secrets)
 
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
 
         self.assertEqual(channel.json_body, {"nonce": "abcd"})
 
@@ -99,7 +97,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         only last for SALT_TIMEOUT (60s).
         """
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         # 59 seconds
@@ -107,7 +104,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
 
         body = json.dumps({"nonce": nonce})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("username must be specified", channel.json_body["error"])
@@ -116,7 +112,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         self.reactor.advance(2)
 
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("unrecognised nonce", channel.json_body["error"])
@@ -126,7 +121,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         Only the provided nonce can be used, as it's checked in the MAC.
         """
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
@@ -143,7 +137,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("HMAC incorrect", channel.json_body["error"])
@@ -154,7 +147,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         user is registered.
         """
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
@@ -174,7 +166,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["user_id"])
@@ -184,7 +175,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         A valid unrecognised nonce.
         """
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
@@ -201,14 +191,12 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["user_id"])
 
         # Now, try and reuse it
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("unrecognised nonce", channel.json_body["error"])
@@ -222,7 +210,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
 
         def nonce():
             request, channel = self.make_request("GET", self.url)
-            self.render(request)
             return channel.json_body["nonce"]
 
         #
@@ -232,7 +219,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Must be present
         body = json.dumps({})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("nonce must be specified", channel.json_body["error"])
@@ -244,7 +230,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Must be present
         body = json.dumps({"nonce": nonce()})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("username must be specified", channel.json_body["error"])
@@ -252,7 +237,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Must be a string
         body = json.dumps({"nonce": nonce(), "username": 1234})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("Invalid username", channel.json_body["error"])
@@ -260,7 +244,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Must not have null bytes
         body = json.dumps({"nonce": nonce(), "username": "abcd\u0000"})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("Invalid username", channel.json_body["error"])
@@ -268,7 +251,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Must not have null bytes
         body = json.dumps({"nonce": nonce(), "username": "a" * 1000})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("Invalid username", channel.json_body["error"])
@@ -280,7 +262,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Must be present
         body = json.dumps({"nonce": nonce(), "username": "a"})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("password must be specified", channel.json_body["error"])
@@ -288,7 +269,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Must be a string
         body = json.dumps({"nonce": nonce(), "username": "a", "password": 1234})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("Invalid password", channel.json_body["error"])
@@ -296,7 +276,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Must not have null bytes
         body = json.dumps({"nonce": nonce(), "username": "a", "password": "abcd\u0000"})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("Invalid password", channel.json_body["error"])
@@ -304,7 +283,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
         # Super long
         body = json.dumps({"nonce": nonce(), "username": "a", "password": "A" * 1000})
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("Invalid password", channel.json_body["error"])
@@ -323,7 +301,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("Invalid user type", channel.json_body["error"])
@@ -335,7 +312,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
 
         # set no displayname
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
@@ -346,19 +322,16 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             {"nonce": nonce, "username": "bob1", "password": "abc123", "mac": want_mac}
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob1:test", channel.json_body["user_id"])
 
         request, channel = self.make_request("GET", "/profile/@bob1:test/displayname")
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("bob1", channel.json_body["displayname"])
 
         # displayname is None
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
@@ -375,19 +348,16 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob2:test", channel.json_body["user_id"])
 
         request, channel = self.make_request("GET", "/profile/@bob2:test/displayname")
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("bob2", channel.json_body["displayname"])
 
         # displayname is empty
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
@@ -404,18 +374,15 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob3:test", channel.json_body["user_id"])
 
         request, channel = self.make_request("GET", "/profile/@bob3:test/displayname")
-        self.render(request)
         self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])
 
         # set displayname
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
@@ -432,13 +399,11 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob4:test", channel.json_body["user_id"])
 
         request, channel = self.make_request("GET", "/profile/@bob4:test/displayname")
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("Bob's Name", channel.json_body["displayname"])
 
@@ -465,7 +430,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
 
         # Register new user with admin API
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         nonce = channel.json_body["nonce"]
 
         want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1)
@@ -485,7 +449,6 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request("POST", self.url, body.encode("utf8"))
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["user_id"])
@@ -511,7 +474,6 @@ class UsersListTestCase(unittest.HomeserverTestCase):
         Try to list users without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("M_MISSING_TOKEN", channel.json_body["errcode"])
@@ -526,7 +488,6 @@ class UsersListTestCase(unittest.HomeserverTestCase):
             b"{}",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(3, len(channel.json_body["users"]))
@@ -562,7 +523,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("You are not a server admin", channel.json_body["error"])
@@ -570,7 +530,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url, access_token=self.other_user_token, content=b"{}",
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("You are not a server admin", channel.json_body["error"])
@@ -585,7 +544,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             "/_synapse/admin/v2/users/@unknown_person:test",
             access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual("M_NOT_FOUND", channel.json_body["errcode"])
@@ -613,7 +571,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -626,7 +583,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -659,7 +615,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -672,7 +627,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -700,7 +654,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/sync", access_token=self.admin_user_tok
         )
-        self.render(request)
 
         if channel.code != 200:
             raise HttpResponseException(
@@ -729,7 +682,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -769,7 +721,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         # Admin user is not blocked by mau anymore
         self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
@@ -807,7 +758,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -852,7 +802,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -879,7 +828,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
@@ -897,7 +845,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -907,7 +854,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url_other_user, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -929,7 +875,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -940,7 +885,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url_other_user, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -961,7 +905,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -972,7 +915,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url_other_user, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -990,7 +932,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=json.dumps({"deactivated": True}).encode(encoding="utf_8"),
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self._is_erased("@user:test", False)
         d = self.store.mark_user_erased("@user:test")
@@ -1004,7 +945,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=json.dumps({"deactivated": False}).encode(encoding="utf_8"),
         )
-        self.render(request)
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
 
         # Reactivate the user.
@@ -1016,14 +956,12 @@ class UserRestTestCase(unittest.HomeserverTestCase):
                 encoding="utf_8"
             ),
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Get user
         request, channel = self.make_request(
             "GET", self.url_other_user, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -1044,7 +982,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -1054,7 +991,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url_other_user, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@user:test", channel.json_body["name"])
@@ -1076,7 +1012,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(201, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -1086,7 +1021,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -1102,7 +1036,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
             access_token=self.admin_user_tok,
             content=body.encode(encoding="utf_8"),
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
 
@@ -1110,7 +1043,6 @@ class UserRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("@bob:test", channel.json_body["name"])
@@ -1153,7 +1085,6 @@ class UserMembershipRestTestCase(unittest.HomeserverTestCase):
         Try to list rooms of an user without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -1167,7 +1098,6 @@ class UserMembershipRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -1180,7 +1110,6 @@ class UserMembershipRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -1194,7 +1123,6 @@ class UserMembershipRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only lookup local users", channel.json_body["error"])
@@ -1208,7 +1136,6 @@ class UserMembershipRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
@@ -1228,7 +1155,6 @@ class UserMembershipRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(number_rooms, channel.json_body["total"])
@@ -1258,7 +1184,6 @@ class PushersRestTestCase(unittest.HomeserverTestCase):
         Try to list pushers of an user without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -1272,7 +1197,6 @@ class PushersRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -1285,7 +1209,6 @@ class PushersRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -1299,7 +1222,6 @@ class PushersRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only lookup local users", channel.json_body["error"])
@@ -1313,7 +1235,6 @@ class PushersRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
@@ -1343,7 +1264,6 @@ class PushersRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(1, channel.json_body["total"])
@@ -1383,7 +1303,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         Try to list media of an user without authentication.
         """
         request, channel = self.make_request("GET", self.url, b"{}")
-        self.render(request)
 
         self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
@@ -1397,7 +1316,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=other_user_token,
         )
-        self.render(request)
 
         self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -1410,7 +1328,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(404, channel.code, msg=channel.json_body)
         self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
@@ -1424,7 +1341,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, channel.code, msg=channel.json_body)
         self.assertEqual("Can only lookup local users", channel.json_body["error"])
@@ -1441,7 +1357,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_media)
@@ -1461,7 +1376,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_media)
@@ -1481,7 +1395,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=5&limit=10", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_media)
@@ -1497,7 +1410,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=-5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -1510,7 +1422,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=-5", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
@@ -1529,7 +1440,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=20", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_media)
@@ -1541,7 +1451,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=21", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_media)
@@ -1553,7 +1462,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?limit=19", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_media)
@@ -1566,7 +1474,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url + "?from=19", access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(channel.json_body["total"], number_media)
@@ -1582,7 +1489,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(0, channel.json_body["total"])
@@ -1600,7 +1506,6 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url, access_token=self.admin_user_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, channel.code, msg=channel.json_body)
         self.assertEqual(number_media, channel.json_body["total"])
@@ -1638,3 +1543,244 @@ class UserMediaRestTestCase(unittest.HomeserverTestCase):
             self.assertIn("last_access_ts", m)
             self.assertIn("quarantined_by", m)
             self.assertIn("safe_from_quarantine", m)
+
+
+class UserTokenRestTestCase(unittest.HomeserverTestCase):
+    """Test for /_synapse/admin/v1/users/<user>/login
+    """
+
+    servlets = [
+        synapse.rest.admin.register_servlets,
+        login.register_servlets,
+        sync.register_servlets,
+        room.register_servlets,
+        devices.register_servlets,
+        logout.register_servlets,
+    ]
+
+    def prepare(self, reactor, clock, hs):
+        self.store = hs.get_datastore()
+
+        self.admin_user = self.register_user("admin", "pass", admin=True)
+        self.admin_user_tok = self.login("admin", "pass")
+
+        self.other_user = self.register_user("user", "pass")
+        self.other_user_tok = self.login("user", "pass")
+        self.url = "/_synapse/admin/v1/users/%s/login" % urllib.parse.quote(
+            self.other_user
+        )
+
+    def _get_token(self) -> str:
+        request, channel = self.make_request(
+            "POST", self.url, b"{}", access_token=self.admin_user_tok
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+        return channel.json_body["access_token"]
+
+    def test_no_auth(self):
+        """Try to login as a user without authentication.
+        """
+        request, channel = self.make_request("POST", self.url, b"{}")
+        self.render(request)
+
+        self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
+        self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
+
+    def test_not_admin(self):
+        """Try to login as a user as a non-admin user.
+        """
+        request, channel = self.make_request(
+            "POST", self.url, b"{}", access_token=self.other_user_tok
+        )
+        self.render(request)
+
+        self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
+
+    def test_send_event(self):
+        """Test that sending event as a user works.
+        """
+        # Create a room.
+        room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_tok)
+
+        # Login in as the user
+        puppet_token = self._get_token()
+
+        # Test that sending works, and generates the event as the right user.
+        resp = self.helper.send_event(room_id, "com.example.test", tok=puppet_token)
+        event_id = resp["event_id"]
+        event = self.get_success(self.store.get_event(event_id))
+        self.assertEqual(event.sender, self.other_user)
+
+    def test_devices(self):
+        """Tests that logging in as a user doesn't create a new device for them.
+        """
+        # Login in as the user
+        self._get_token()
+
+        # Check that we don't see a new device in our devices list
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=self.other_user_tok
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+        # We should only see the one device (from the login in `prepare`)
+        self.assertEqual(len(channel.json_body["devices"]), 1)
+
+    def test_logout(self):
+        """Test that calling `/logout` with the token works.
+        """
+        # Login in as the user
+        puppet_token = self._get_token()
+
+        # Test that we can successfully make a request
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=puppet_token
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+        # Logout with the puppet token
+        request, channel = self.make_request(
+            "POST", "logout", b"{}", access_token=puppet_token
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+        # The puppet token should no longer work
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=puppet_token
+        )
+        self.render(request)
+        self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
+
+        # .. but the real user's tokens should still work
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=self.other_user_tok
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+    def test_user_logout_all(self):
+        """Tests that the target user calling `/logout/all` does *not* expire
+        the token.
+        """
+        # Login in as the user
+        puppet_token = self._get_token()
+
+        # Test that we can successfully make a request
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=puppet_token
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+        # Logout all with the real user token
+        request, channel = self.make_request(
+            "POST", "logout/all", b"{}", access_token=self.other_user_tok
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+        # The puppet token should still work
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=puppet_token
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+        # .. but the real user's tokens shouldn't
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=self.other_user_tok
+        )
+        self.render(request)
+        self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
+
+    def test_admin_logout_all(self):
+        """Tests that the admin user calling `/logout/all` does expire the
+        token.
+        """
+        # Login in as the user
+        puppet_token = self._get_token()
+
+        # Test that we can successfully make a request
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=puppet_token
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+        # Logout all with the admin user token
+        request, channel = self.make_request(
+            "POST", "logout/all", b"{}", access_token=self.admin_user_tok
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+        # The puppet token should no longer work
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=puppet_token
+        )
+        self.render(request)
+        self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
+
+        # .. but the real user's tokens should still work
+        request, channel = self.make_request(
+            "GET", "devices", b"{}", access_token=self.other_user_tok
+        )
+        self.render(request)
+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
+
+    @unittest.override_config(
+        {
+            "public_baseurl": "https://example.org/",
+            "user_consent": {
+                "version": "1.0",
+                "policy_name": "My Cool Privacy Policy",
+                "template_dir": "/",
+                "require_at_registration": True,
+                "block_events_error": "You should accept the policy",
+            },
+            "form_secret": "123secret",
+        }
+    )
+    def test_consent(self):
+        """Test that sending a message is not subject to the privacy policies.
+        """
+        # Have the admin user accept the terms.
+        self.get_success(self.store.user_set_consent_version(self.admin_user, "1.0"))
+
+        # First, cheekily accept the terms and create a room
+        self.get_success(self.store.user_set_consent_version(self.other_user, "1.0"))
+        room_id = self.helper.create_room_as(self.other_user, tok=self.other_user_tok)
+        self.helper.send_event(room_id, "com.example.test", tok=self.other_user_tok)
+
+        # Now unaccept it and check that we can't send an event
+        self.get_success(self.store.user_set_consent_version(self.other_user, "0.0"))
+        self.helper.send_event(
+            room_id, "com.example.test", tok=self.other_user_tok, expect_code=403
+        )
+
+        # Login in as the user
+        puppet_token = self._get_token()
+
+        # Sending an event on their behalf should work fine
+        self.helper.send_event(room_id, "com.example.test", tok=puppet_token)
+
+    @override_config(
+        {"limit_usage_by_mau": True, "max_mau_value": 1, "mau_trial_days": 0}
+    )
+    def test_mau_limit(self):
+        # Create a room as the admin user. This will bump the monthly active users to 1.
+        room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
+
+        # Trying to join as the other user should fail due to reaching MAU limit.
+        self.helper.join(
+            room_id, user=self.other_user, tok=self.other_user_tok, expect_code=403
+        )
+
+        # Logging in as the other user and joining a room should work, even
+        # though the MAU limit would stop the user doing so.
+        puppet_token = self._get_token()
+        self.helper.join(room_id, user=self.other_user, tok=puppet_token)
diff --git a/tests/rest/client/test_consent.py b/tests/rest/client/test_consent.py
index 6803b372ac..e2e6a5e16d 100644
--- a/tests/rest/client/test_consent.py
+++ b/tests/rest/client/test_consent.py
@@ -21,7 +21,7 @@ from synapse.rest.client.v1 import login, room
 from synapse.rest.consent import consent_resource
 
 from tests import unittest
-from tests.server import render
+from tests.server import FakeSite, make_request
 
 
 class ConsentResourceTestCase(unittest.HomeserverTestCase):
@@ -61,8 +61,9 @@ class ConsentResourceTestCase(unittest.HomeserverTestCase):
     def test_render_public_consent(self):
         """You can observe the terms form without specifying a user"""
         resource = consent_resource.ConsentResource(self.hs)
-        request, channel = self.make_request("GET", "/consent?v=1", shorthand=False)
-        render(request, resource, self.reactor)
+        request, channel = make_request(
+            self.reactor, FakeSite(resource), "GET", "/consent?v=1", shorthand=False
+        )
         self.assertEqual(channel.code, 200)
 
     def test_accept_consent(self):
@@ -81,10 +82,14 @@ class ConsentResourceTestCase(unittest.HomeserverTestCase):
             uri_builder.build_user_consent_uri(user_id).replace("_matrix/", "")
             + "&u=user"
         )
-        request, channel = self.make_request(
-            "GET", consent_uri, access_token=access_token, shorthand=False
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(resource),
+            "GET",
+            consent_uri,
+            access_token=access_token,
+            shorthand=False,
         )
-        render(request, resource, self.reactor)
         self.assertEqual(channel.code, 200)
 
         # Get the version from the body, and whether we've consented
@@ -92,21 +97,26 @@ class ConsentResourceTestCase(unittest.HomeserverTestCase):
         self.assertEqual(consented, "False")
 
         # POST to the consent page, saying we've agreed
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(resource),
             "POST",
             consent_uri + "&v=" + version,
             access_token=access_token,
             shorthand=False,
         )
-        render(request, resource, self.reactor)
         self.assertEqual(channel.code, 200)
 
         # Fetch the consent page, to get the consent version -- it should have
         # changed
-        request, channel = self.make_request(
-            "GET", consent_uri, access_token=access_token, shorthand=False
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(resource),
+            "GET",
+            consent_uri,
+            access_token=access_token,
+            shorthand=False,
         )
-        render(request, resource, self.reactor)
         self.assertEqual(channel.code, 200)
 
         # Get the version from the body, and check that it's the version we
diff --git a/tests/rest/client/test_ephemeral_message.py b/tests/rest/client/test_ephemeral_message.py
index 5e9c07ebf3..a1ccc4ee9a 100644
--- a/tests/rest/client/test_ephemeral_message.py
+++ b/tests/rest/client/test_ephemeral_message.py
@@ -94,7 +94,6 @@ class EphemeralMessageTestCase(unittest.HomeserverTestCase):
         url = "/_matrix/client/r0/rooms/%s/event/%s" % (room_id, event_id)
 
         request, channel = self.make_request("GET", url)
-        self.render(request)
 
         self.assertEqual(channel.code, expected_code, channel.result)
 
diff --git a/tests/rest/client/test_identity.py b/tests/rest/client/test_identity.py
index c973521907..259c6a1985 100644
--- a/tests/rest/client/test_identity.py
+++ b/tests/rest/client/test_identity.py
@@ -46,7 +46,6 @@ class IdentityTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", "/createRoom", b"{}", access_token=tok
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
         room_id = channel.json_body["room_id"]
 
@@ -60,5 +59,4 @@ class IdentityTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", request_url, request_data, access_token=tok
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"403", channel.result)
diff --git a/tests/rest/client/test_redactions.py b/tests/rest/client/test_redactions.py
index d2bcf256fa..c1f516cc93 100644
--- a/tests/rest/client/test_redactions.py
+++ b/tests/rest/client/test_redactions.py
@@ -72,7 +72,6 @@ class RedactionsTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "POST", path, content={}, access_token=access_token
         )
-        self.render(request)
         self.assertEqual(int(channel.result["code"]), expect_code)
         return channel.json_body
 
@@ -80,7 +79,6 @@ class RedactionsTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "sync", access_token=self.mod_access_token
         )
-        self.render(request)
         self.assertEqual(channel.result["code"], b"200")
         room_sync = channel.json_body["rooms"]["join"][room_id]
         return room_sync["timeline"]["events"]
diff --git a/tests/rest/client/test_retention.py b/tests/rest/client/test_retention.py
index 7d3773ff78..f56b5d9231 100644
--- a/tests/rest/client/test_retention.py
+++ b/tests/rest/client/test_retention.py
@@ -326,7 +326,6 @@ class RetentionNoDefaultPolicyTestCase(unittest.HomeserverTestCase):
         url = "/_matrix/client/r0/rooms/%s/event/%s" % (room_id, event_id)
 
         request, channel = self.make_request("GET", url, access_token=self.token)
-        self.render(request)
 
         self.assertEqual(channel.code, expected_code, channel.result)
 
diff --git a/tests/rest/client/test_shadow_banned.py b/tests/rest/client/test_shadow_banned.py
index 6bb02b9630..94dcfb9f7c 100644
--- a/tests/rest/client/test_shadow_banned.py
+++ b/tests/rest/client/test_shadow_banned.py
@@ -95,7 +95,6 @@ class RoomTestCase(_ShadowBannedBase):
             {"id_server": "test", "medium": "email", "address": "test@test.test"},
             access_token=self.banned_access_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
         # This should have raised an error earlier, but double check this wasn't called.
@@ -110,7 +109,6 @@ class RoomTestCase(_ShadowBannedBase):
             {"visibility": "public", "invite": [self.other_user_id]},
             access_token=self.banned_access_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         room_id = channel.json_body["room_id"]
 
@@ -166,7 +164,6 @@ class RoomTestCase(_ShadowBannedBase):
             {"new_version": "6"},
             access_token=self.banned_access_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         # A new room_id should be returned.
         self.assertIn("replacement_room", channel.json_body)
@@ -192,7 +189,6 @@ class RoomTestCase(_ShadowBannedBase):
             {"typing": True, "timeout": 30000},
             access_token=self.banned_access_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         # There should be no typing events.
@@ -208,7 +204,6 @@ class RoomTestCase(_ShadowBannedBase):
             {"typing": True, "timeout": 30000},
             access_token=self.other_access_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         # These appear in the room.
@@ -255,7 +250,6 @@ class ProfileTestCase(_ShadowBannedBase):
             {"displayname": new_display_name},
             access_token=self.banned_access_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         self.assertEqual(channel.json_body, {})
 
@@ -263,7 +257,6 @@ class ProfileTestCase(_ShadowBannedBase):
         request, channel = self.make_request(
             "GET", "/profile/%s/displayname" % (self.banned_user_id,)
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
         self.assertEqual(channel.json_body["displayname"], new_display_name)
 
@@ -296,7 +289,6 @@ class ProfileTestCase(_ShadowBannedBase):
             {"membership": "join", "displayname": new_display_name},
             access_token=self.banned_access_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         self.assertIn("event_id", channel.json_body)
 
diff --git a/tests/rest/client/test_third_party_rules.py b/tests/rest/client/test_third_party_rules.py
index 0048bea54a..0e96697f9b 100644
--- a/tests/rest/client/test_third_party_rules.py
+++ b/tests/rest/client/test_third_party_rules.py
@@ -92,7 +92,6 @@ class ThirdPartyRulesTestCase(unittest.HomeserverTestCase):
             {},
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         callback.assert_called_once()
@@ -111,7 +110,6 @@ class ThirdPartyRulesTestCase(unittest.HomeserverTestCase):
             {},
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"403", channel.result)
 
     def test_cannot_modify_event(self):
@@ -131,7 +129,6 @@ class ThirdPartyRulesTestCase(unittest.HomeserverTestCase):
             {"x": "x"},
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.result["code"], b"500", channel.result)
 
     def test_modify_event(self):
@@ -151,7 +148,6 @@ class ThirdPartyRulesTestCase(unittest.HomeserverTestCase):
             {"x": "x"},
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.result["code"], b"200", channel.result)
         event_id = channel.json_body["event_id"]
 
@@ -161,7 +157,6 @@ class ThirdPartyRulesTestCase(unittest.HomeserverTestCase):
             "/_matrix/client/r0/rooms/%s/event/%s" % (self.room_id, event_id),
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.result["code"], b"200", channel.result)
         ev = channel.json_body
         self.assertEqual(ev["content"]["x"], "y")
diff --git a/tests/rest/client/v1/test_directory.py b/tests/rest/client/v1/test_directory.py
index ea5a7f3739..7a2c653df8 100644
--- a/tests/rest/client/v1/test_directory.py
+++ b/tests/rest/client/v1/test_directory.py
@@ -94,7 +94,6 @@ class DirectoryTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url, request_data, access_token=self.user_tok
         )
-        self.render(request)
         self.assertEqual(channel.code, 400, channel.result)
 
     def test_room_creation(self):
@@ -108,7 +107,6 @@ class DirectoryTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", url, request_data, access_token=self.user_tok
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
     def set_alias_via_state_event(self, expected_code, alias_length=5):
@@ -123,7 +121,6 @@ class DirectoryTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url, request_data, access_token=self.user_tok
         )
-        self.render(request)
         self.assertEqual(channel.code, expected_code, channel.result)
 
     def set_alias_via_directory(self, expected_code, alias_length=5):
@@ -134,7 +131,6 @@ class DirectoryTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url, request_data, access_token=self.user_tok
         )
-        self.render(request)
         self.assertEqual(channel.code, expected_code, channel.result)
 
     def random_alias(self, length):
diff --git a/tests/rest/client/v1/test_events.py b/tests/rest/client/v1/test_events.py
index 3397ba5579..12a93f5687 100644
--- a/tests/rest/client/v1/test_events.py
+++ b/tests/rest/client/v1/test_events.py
@@ -66,14 +66,12 @@ class EventStreamPermissionsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/events?access_token=%s" % ("invalid" + self.token,)
         )
-        self.render(request)
         self.assertEquals(channel.code, 401, msg=channel.result)
 
         # valid token, expect content
         request, channel = self.make_request(
             "GET", "/events?access_token=%s&timeout=0" % (self.token,)
         )
-        self.render(request)
         self.assertEquals(channel.code, 200, msg=channel.result)
         self.assertTrue("chunk" in channel.json_body)
         self.assertTrue("start" in channel.json_body)
@@ -92,7 +90,6 @@ class EventStreamPermissionsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/events?access_token=%s&timeout=0" % (self.token,)
         )
-        self.render(request)
         self.assertEquals(channel.code, 200, msg=channel.result)
 
         # We may get a presence event for ourselves down
@@ -155,5 +152,4 @@ class GetEventsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/events/" + event_id, access_token=self.token,
         )
-        self.render(request)
         self.assertEquals(channel.code, 200, msg=channel.result)
diff --git a/tests/rest/client/v1/test_login.py b/tests/rest/client/v1/test_login.py
index 5d987a30c7..176ddf7ec9 100644
--- a/tests/rest/client/v1/test_login.py
+++ b/tests/rest/client/v1/test_login.py
@@ -64,7 +64,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
                 "password": "monkey",
             }
             request, channel = self.make_request(b"POST", LOGIN_URL, params)
-            self.render(request)
 
             if i == 5:
                 self.assertEquals(channel.result["code"], b"429", channel.result)
@@ -84,7 +83,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
             "password": "monkey",
         }
         request, channel = self.make_request(b"POST", LOGIN_URL, params)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
@@ -111,7 +109,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
                 "password": "monkey",
             }
             request, channel = self.make_request(b"POST", LOGIN_URL, params)
-            self.render(request)
 
             if i == 5:
                 self.assertEquals(channel.result["code"], b"429", channel.result)
@@ -131,7 +128,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
             "password": "monkey",
         }
         request, channel = self.make_request(b"POST", LOGIN_URL, params)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
@@ -158,7 +154,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
                 "password": "notamonkey",
             }
             request, channel = self.make_request(b"POST", LOGIN_URL, params)
-            self.render(request)
 
             if i == 5:
                 self.assertEquals(channel.result["code"], b"429", channel.result)
@@ -178,7 +173,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
             "password": "notamonkey",
         }
         request, channel = self.make_request(b"POST", LOGIN_URL, params)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"403", channel.result)
 
@@ -188,7 +182,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
 
         # we shouldn't be able to make requests without an access token
         request, channel = self.make_request(b"GET", TEST_URL)
-        self.render(request)
         self.assertEquals(channel.result["code"], b"401", channel.result)
         self.assertEquals(channel.json_body["errcode"], "M_MISSING_TOKEN")
 
@@ -199,7 +192,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
             "password": "monkey",
         }
         request, channel = self.make_request(b"POST", LOGIN_URL, params)
-        self.render(request)
 
         self.assertEquals(channel.code, 200, channel.result)
         access_token = channel.json_body["access_token"]
@@ -209,7 +201,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"GET", TEST_URL, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 200, channel.result)
 
         # time passes
@@ -219,7 +210,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"GET", TEST_URL, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 401, channel.result)
         self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
         self.assertEquals(channel.json_body["soft_logout"], True)
@@ -236,7 +226,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"GET", TEST_URL, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 401, channel.result)
         self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
         self.assertEquals(channel.json_body["soft_logout"], True)
@@ -247,7 +236,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"GET", TEST_URL, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 401, channel.result)
         self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
         self.assertEquals(channel.json_body["soft_logout"], False)
@@ -257,7 +245,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"DELETE", "devices/" + device_id, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 401, channel.result)
         # check it's a UI-Auth fail
         self.assertEqual(
@@ -281,7 +268,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
             access_token=access_token,
             content={"auth": auth},
         )
-        self.render(request)
         self.assertEquals(channel.code, 200, channel.result)
 
     @override_config({"session_lifetime": "24h"})
@@ -295,7 +281,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"GET", TEST_URL, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 200, channel.result)
 
         # time passes
@@ -305,7 +290,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"GET", TEST_URL, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 401, channel.result)
         self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
         self.assertEquals(channel.json_body["soft_logout"], True)
@@ -314,7 +298,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", "/logout", access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
     @override_config({"session_lifetime": "24h"})
@@ -328,7 +311,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"GET", TEST_URL, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 200, channel.result)
 
         # time passes
@@ -338,7 +320,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"GET", TEST_URL, access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.code, 401, channel.result)
         self.assertEquals(channel.json_body["errcode"], "M_UNKNOWN_TOKEN")
         self.assertEquals(channel.json_body["soft_logout"], True)
@@ -347,7 +328,6 @@ class LoginRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", "/logout/all", access_token=access_token
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
 
@@ -423,7 +403,6 @@ class CASTestCase(unittest.HomeserverTestCase):
 
         # Get Synapse to call the fake CAS and serve the template.
         request, channel = self.make_request("GET", cas_ticket_url)
-        self.render(request)
 
         # Test that the response is HTML.
         self.assertEqual(channel.code, 200)
@@ -468,7 +447,6 @@ class CASTestCase(unittest.HomeserverTestCase):
 
         # Get Synapse to call the fake CAS and serve the template.
         request, channel = self.make_request("GET", cas_ticket_url)
-        self.render(request)
 
         self.assertEqual(channel.code, 302)
         location_headers = channel.headers.getRawHeaders("Location")
@@ -495,7 +473,6 @@ class CASTestCase(unittest.HomeserverTestCase):
 
         # Get Synapse to call the fake CAS and serve the template.
         request, channel = self.make_request("GET", cas_ticket_url)
-        self.render(request)
 
         # Because the user is deactivated they are served an error template.
         self.assertEqual(channel.code, 403)
@@ -526,7 +503,6 @@ class JWTTestCase(unittest.HomeserverTestCase):
             {"type": "org.matrix.login.jwt", "token": self.jwt_encode(*args)}
         )
         request, channel = self.make_request(b"POST", LOGIN_URL, params)
-        self.render(request)
         return channel
 
     def test_login_jwt_valid_registered(self):
@@ -659,7 +635,6 @@ class JWTTestCase(unittest.HomeserverTestCase):
     def test_login_no_token(self):
         params = json.dumps({"type": "org.matrix.login.jwt"})
         request, channel = self.make_request(b"POST", LOGIN_URL, params)
-        self.render(request)
         self.assertEqual(channel.result["code"], b"403", channel.result)
         self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
         self.assertEqual(channel.json_body["error"], "Token field for JWT is missing")
@@ -733,7 +708,6 @@ class JWTPubKeyTestCase(unittest.HomeserverTestCase):
             {"type": "org.matrix.login.jwt", "token": self.jwt_encode(*args)}
         )
         request, channel = self.make_request(b"POST", LOGIN_URL, params)
-        self.render(request)
         return channel
 
     def test_login_jwt_valid(self):
@@ -766,7 +740,6 @@ class AppserviceLoginRestServletTestCase(unittest.HomeserverTestCase):
             "/_matrix/client/r0/register?access_token=%s" % (self.service.token,),
             {"username": username},
         )
-        self.render(request)
 
     def make_homeserver(self, reactor, clock):
         self.hs = self.setup_test_homeserver()
@@ -815,7 +788,6 @@ class AppserviceLoginRestServletTestCase(unittest.HomeserverTestCase):
             b"POST", LOGIN_URL, params, access_token=self.service.token
         )
 
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
     def test_login_appservice_user_bot(self):
@@ -831,7 +803,6 @@ class AppserviceLoginRestServletTestCase(unittest.HomeserverTestCase):
             b"POST", LOGIN_URL, params, access_token=self.service.token
         )
 
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
     def test_login_appservice_wrong_user(self):
@@ -847,7 +818,6 @@ class AppserviceLoginRestServletTestCase(unittest.HomeserverTestCase):
             b"POST", LOGIN_URL, params, access_token=self.service.token
         )
 
-        self.render(request)
         self.assertEquals(channel.result["code"], b"403", channel.result)
 
     def test_login_appservice_wrong_as(self):
@@ -863,7 +833,6 @@ class AppserviceLoginRestServletTestCase(unittest.HomeserverTestCase):
             b"POST", LOGIN_URL, params, access_token=self.another_service.token
         )
 
-        self.render(request)
         self.assertEquals(channel.result["code"], b"403", channel.result)
 
     def test_login_appservice_no_token(self):
@@ -878,5 +847,4 @@ class AppserviceLoginRestServletTestCase(unittest.HomeserverTestCase):
         }
         request, channel = self.make_request(b"POST", LOGIN_URL, params)
 
-        self.render(request)
         self.assertEquals(channel.result["code"], b"401", channel.result)
diff --git a/tests/rest/client/v1/test_presence.py b/tests/rest/client/v1/test_presence.py
index 3c66255dac..b84f86d28c 100644
--- a/tests/rest/client/v1/test_presence.py
+++ b/tests/rest/client/v1/test_presence.py
@@ -53,7 +53,6 @@ class PresenceTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/presence/%s/status" % (self.user_id,), body
         )
-        self.render(request)
 
         self.assertEqual(channel.code, 200)
         self.assertEqual(self.hs.presence_handler.set_state.call_count, 1)
@@ -69,7 +68,6 @@ class PresenceTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/presence/%s/status" % (self.user_id,), body
         )
-        self.render(request)
 
         self.assertEqual(channel.code, 200)
         self.assertEqual(self.hs.presence_handler.set_state.call_count, 0)
diff --git a/tests/rest/client/v1/test_profile.py b/tests/rest/client/v1/test_profile.py
index ace0a3c08d..383a9eafac 100644
--- a/tests/rest/client/v1/test_profile.py
+++ b/tests/rest/client/v1/test_profile.py
@@ -195,7 +195,6 @@ class ProfileTestCase(unittest.HomeserverTestCase):
             content=json.dumps({"displayname": "test"}),
             access_token=self.owner_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         res = self.get_displayname()
@@ -209,7 +208,6 @@ class ProfileTestCase(unittest.HomeserverTestCase):
             content=json.dumps({"displayname": "test" * 100}),
             access_token=self.owner_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 400, channel.result)
 
         res = self.get_displayname()
@@ -219,7 +217,6 @@ class ProfileTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/profile/%s/displayname" % (self.owner,)
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
         return channel.json_body["displayname"]
 
@@ -284,7 +281,6 @@ class ProfilesRestrictedTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.profile_url + url_suffix, access_token=access_token
         )
-        self.render(request)
         self.assertEqual(channel.code, expected_code, channel.result)
 
     def ensure_requester_left_room(self):
@@ -327,7 +323,6 @@ class OwnProfileUnrestrictedTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/profile/" + self.requester, access_token=self.requester_tok
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         request, channel = self.make_request(
@@ -335,7 +330,6 @@ class OwnProfileUnrestrictedTestCase(unittest.HomeserverTestCase):
             "/profile/" + self.requester + "/displayname",
             access_token=self.requester_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         request, channel = self.make_request(
@@ -343,5 +337,4 @@ class OwnProfileUnrestrictedTestCase(unittest.HomeserverTestCase):
             "/profile/" + self.requester + "/avatar_url",
             access_token=self.requester_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
diff --git a/tests/rest/client/v1/test_push_rule_attrs.py b/tests/rest/client/v1/test_push_rule_attrs.py
index 081052f6a6..7add5523c8 100644
--- a/tests/rest/client/v1/test_push_rule_attrs.py
+++ b/tests/rest/client/v1/test_push_rule_attrs.py
@@ -48,14 +48,12 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/pushrules/global/override/best.friend", body, access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # GET enabled for that new rule
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
         self.assertEqual(channel.json_body["enabled"], True)
 
@@ -79,7 +77,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/pushrules/global/override/best.friend", body, access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # disable the rule
@@ -89,14 +86,12 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
             {"enabled": False},
             access_token=token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # check rule disabled
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
         self.assertEqual(channel.json_body["enabled"], False)
 
@@ -104,21 +99,18 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", "/pushrules/global/override/best.friend", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # PUT a new rule
         request, channel = self.make_request(
             "PUT", "/pushrules/global/override/best.friend", body, access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # GET enabled for that new rule
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
         self.assertEqual(channel.json_body["enabled"], True)
 
@@ -141,7 +133,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/pushrules/global/override/best.friend", body, access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # disable the rule
@@ -151,14 +142,12 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
             {"enabled": False},
             access_token=token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # check rule disabled
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
         self.assertEqual(channel.json_body["enabled"], False)
 
@@ -169,14 +158,12 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
             {"enabled": True},
             access_token=token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # check rule enabled
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
         self.assertEqual(channel.json_body["enabled"], True)
 
@@ -198,7 +185,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -206,28 +192,24 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/pushrules/global/override/best.friend", body, access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # GET enabled for that new rule
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # DELETE the rule
         request, channel = self.make_request(
             "DELETE", "/pushrules/global/override/best.friend", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # check 404 for deleted rule
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -242,7 +224,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/.m.muahahaha/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -260,7 +241,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
             {"enabled": True},
             access_token=token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -278,7 +258,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
             {"enabled": True},
             access_token=token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -300,14 +279,12 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/pushrules/global/override/best.friend", body, access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # GET actions for that new rule
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/actions", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
         self.assertEqual(
             channel.json_body["actions"], ["notify", {"set_tweak": "highlight"}]
@@ -331,7 +308,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/pushrules/global/override/best.friend", body, access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # change the rule actions
@@ -341,14 +317,12 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
             {"actions": ["dont_notify"]},
             access_token=token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # GET actions for that new rule
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/actions", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
         self.assertEqual(channel.json_body["actions"], ["dont_notify"])
 
@@ -370,7 +344,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -378,21 +351,18 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", "/pushrules/global/override/best.friend", body, access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # DELETE the rule
         request, channel = self.make_request(
             "DELETE", "/pushrules/global/override/best.friend", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # check 404 for deleted rule
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/best.friend/enabled", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -407,7 +377,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/pushrules/global/override/.m.muahahaha/actions", access_token=token
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -425,7 +394,6 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
             {"actions": ["dont_notify"]},
             access_token=token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
 
@@ -443,6 +411,5 @@ class PushRuleAttributesTestCase(HomeserverTestCase):
             {"actions": ["dont_notify"]},
             access_token=token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 404)
         self.assertEqual(channel.json_body["errcode"], Codes.NOT_FOUND)
diff --git a/tests/rest/client/v1/test_rooms.py b/tests/rest/client/v1/test_rooms.py
index 9ba5f9d943..49f1073c88 100644
--- a/tests/rest/client/v1/test_rooms.py
+++ b/tests/rest/client/v1/test_rooms.py
@@ -86,7 +86,6 @@ class RoomPermissionsTestCase(RoomBase):
         request, channel = self.make_request(
             "PUT", self.created_rmid_msg_path, b'{"msgtype":"m.text","body":"test msg"}'
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
         # set topic for public room
@@ -95,7 +94,6 @@ class RoomPermissionsTestCase(RoomBase):
             ("rooms/%s/state/m.room.topic" % self.created_public_rmid).encode("ascii"),
             b'{"topic":"Public Room Topic"}',
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
         # auth as user_id now
@@ -118,12 +116,10 @@ class RoomPermissionsTestCase(RoomBase):
             "/rooms/%s/send/m.room.message/mid2" % (self.uncreated_rmid,),
             msg_content,
         )
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         # send message in created room not joined (no state), expect 403
         request, channel = self.make_request("PUT", send_msg_path(), msg_content)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         # send message in created room and invited, expect 403
@@ -131,19 +127,16 @@ class RoomPermissionsTestCase(RoomBase):
             room=self.created_rmid, src=self.rmcreator_id, targ=self.user_id
         )
         request, channel = self.make_request("PUT", send_msg_path(), msg_content)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         # send message in created room and joined, expect 200
         self.helper.join(room=self.created_rmid, user=self.user_id)
         request, channel = self.make_request("PUT", send_msg_path(), msg_content)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         # send message in created room and left, expect 403
         self.helper.leave(room=self.created_rmid, user=self.user_id)
         request, channel = self.make_request("PUT", send_msg_path(), msg_content)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
     def test_topic_perms(self):
@@ -154,20 +147,16 @@ class RoomPermissionsTestCase(RoomBase):
         request, channel = self.make_request(
             "PUT", "/rooms/%s/state/m.room.topic" % self.uncreated_rmid, topic_content
         )
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
         request, channel = self.make_request(
             "GET", "/rooms/%s/state/m.room.topic" % self.uncreated_rmid
         )
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         # set/get topic in created PRIVATE room not joined, expect 403
         request, channel = self.make_request("PUT", topic_path, topic_content)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
         request, channel = self.make_request("GET", topic_path)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         # set topic in created PRIVATE room and invited, expect 403
@@ -175,12 +164,10 @@ class RoomPermissionsTestCase(RoomBase):
             room=self.created_rmid, src=self.rmcreator_id, targ=self.user_id
         )
         request, channel = self.make_request("PUT", topic_path, topic_content)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         # get topic in created PRIVATE room and invited, expect 403
         request, channel = self.make_request("GET", topic_path)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         # set/get topic in created PRIVATE room and joined, expect 200
@@ -189,29 +176,24 @@ class RoomPermissionsTestCase(RoomBase):
         # Only room ops can set topic by default
         self.helper.auth_user_id = self.rmcreator_id
         request, channel = self.make_request("PUT", topic_path, topic_content)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
         self.helper.auth_user_id = self.user_id
 
         request, channel = self.make_request("GET", topic_path)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
         self.assert_dict(json.loads(topic_content.decode("utf8")), channel.json_body)
 
         # set/get topic in created PRIVATE room and left, expect 403
         self.helper.leave(room=self.created_rmid, user=self.user_id)
         request, channel = self.make_request("PUT", topic_path, topic_content)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
         request, channel = self.make_request("GET", topic_path)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         # get topic in PUBLIC room, not joined, expect 403
         request, channel = self.make_request(
             "GET", "/rooms/%s/state/m.room.topic" % self.created_public_rmid
         )
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         # set topic in PUBLIC room, not joined, expect 403
@@ -220,14 +202,12 @@ class RoomPermissionsTestCase(RoomBase):
             "/rooms/%s/state/m.room.topic" % self.created_public_rmid,
             topic_content,
         )
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
     def _test_get_membership(self, room=None, members=[], expect_code=None):
         for member in members:
             path = "/rooms/%s/state/m.room.member/%s" % (room, member)
             request, channel = self.make_request("GET", path)
-            self.render(request)
             self.assertEquals(expect_code, channel.code)
 
     def test_membership_basic_room_perms(self):
@@ -400,18 +380,15 @@ class RoomsMemberListTestCase(RoomBase):
     def test_get_member_list(self):
         room_id = self.helper.create_room_as(self.user_id)
         request, channel = self.make_request("GET", "/rooms/%s/members" % room_id)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
     def test_get_member_list_no_room(self):
         request, channel = self.make_request("GET", "/rooms/roomdoesnotexist/members")
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
     def test_get_member_list_no_permission(self):
         room_id = self.helper.create_room_as("@some_other_guy:red")
         request, channel = self.make_request("GET", "/rooms/%s/members" % room_id)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
     def test_get_member_list_mixed_memberships(self):
@@ -421,19 +398,16 @@ class RoomsMemberListTestCase(RoomBase):
         self.helper.invite(room=room_id, src=room_creator, targ=self.user_id)
         # can't see list if you're just invited.
         request, channel = self.make_request("GET", room_path)
-        self.render(request)
         self.assertEquals(403, channel.code, msg=channel.result["body"])
 
         self.helper.join(room=room_id, user=self.user_id)
         # can see list now joined
         request, channel = self.make_request("GET", room_path)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         self.helper.leave(room=room_id, user=self.user_id)
         # can see old list once left
         request, channel = self.make_request("GET", room_path)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
 
@@ -446,7 +420,6 @@ class RoomsCreateTestCase(RoomBase):
         # POST with no config keys, expect new room id
         request, channel = self.make_request("POST", "/createRoom", "{}")
 
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
         self.assertTrue("room_id" in channel.json_body)
 
@@ -455,7 +428,6 @@ class RoomsCreateTestCase(RoomBase):
         request, channel = self.make_request(
             "POST", "/createRoom", b'{"visibility":"private"}'
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         self.assertTrue("room_id" in channel.json_body)
 
@@ -464,7 +436,6 @@ class RoomsCreateTestCase(RoomBase):
         request, channel = self.make_request(
             "POST", "/createRoom", b'{"custom":"stuff"}'
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         self.assertTrue("room_id" in channel.json_body)
 
@@ -473,18 +444,15 @@ class RoomsCreateTestCase(RoomBase):
         request, channel = self.make_request(
             "POST", "/createRoom", b'{"visibility":"private","custom":"things"}'
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         self.assertTrue("room_id" in channel.json_body)
 
     def test_post_room_invalid_content(self):
         # POST with invalid content / paths, expect 400
         request, channel = self.make_request("POST", "/createRoom", b'{"visibili')
-        self.render(request)
         self.assertEquals(400, channel.code)
 
         request, channel = self.make_request("POST", "/createRoom", b'["hello"]')
-        self.render(request)
         self.assertEquals(400, channel.code)
 
     def test_post_room_invitees_invalid_mxid(self):
@@ -493,7 +461,6 @@ class RoomsCreateTestCase(RoomBase):
         request, channel = self.make_request(
             "POST", "/createRoom", b'{"invite":["@alice:example.com "]}'
         )
-        self.render(request)
         self.assertEquals(400, channel.code)
 
 
@@ -510,52 +477,42 @@ class RoomTopicTestCase(RoomBase):
     def test_invalid_puts(self):
         # missing keys or invalid json
         request, channel = self.make_request("PUT", self.path, "{}")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", self.path, '{"_name":"bo"}')
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", self.path, '{"nao')
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request(
             "PUT", self.path, '[{"_name":"bo"},{"_name":"jill"}]'
         )
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", self.path, "text only")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", self.path, "")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         # valid key, wrong type
         content = '{"topic":["Topic name"]}'
         request, channel = self.make_request("PUT", self.path, content)
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
     def test_rooms_topic(self):
         # nothing should be there
         request, channel = self.make_request("GET", self.path)
-        self.render(request)
         self.assertEquals(404, channel.code, msg=channel.result["body"])
 
         # valid put
         content = '{"topic":"Topic name"}'
         request, channel = self.make_request("PUT", self.path, content)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         # valid get
         request, channel = self.make_request("GET", self.path)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
         self.assert_dict(json.loads(content), channel.json_body)
 
@@ -563,12 +520,10 @@ class RoomTopicTestCase(RoomBase):
         # valid put with extra keys
         content = '{"topic":"Seasons","subtopic":"Summer"}'
         request, channel = self.make_request("PUT", self.path, content)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         # valid get
         request, channel = self.make_request("GET", self.path)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
         self.assert_dict(json.loads(content), channel.json_body)
 
@@ -585,29 +540,23 @@ class RoomMemberStateTestCase(RoomBase):
         path = "/rooms/%s/state/m.room.member/%s" % (self.room_id, self.user_id)
         # missing keys or invalid json
         request, channel = self.make_request("PUT", path, "{}")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", path, '{"_name":"bo"}')
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", path, '{"nao')
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request(
             "PUT", path, b'[{"_name":"bo"},{"_name":"jill"}]'
         )
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", path, "text only")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", path, "")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         # valid keys, wrong types
@@ -617,7 +566,6 @@ class RoomMemberStateTestCase(RoomBase):
             Membership.LEAVE,
         )
         request, channel = self.make_request("PUT", path, content.encode("ascii"))
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
     def test_rooms_members_self(self):
@@ -629,11 +577,9 @@ class RoomMemberStateTestCase(RoomBase):
         # valid join message (NOOP since we made the room)
         content = '{"membership":"%s"}' % Membership.JOIN
         request, channel = self.make_request("PUT", path, content.encode("ascii"))
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("GET", path, None)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         expected_response = {"membership": Membership.JOIN}
@@ -649,11 +595,9 @@ class RoomMemberStateTestCase(RoomBase):
         # valid invite message
         content = '{"membership":"%s"}' % Membership.INVITE
         request, channel = self.make_request("PUT", path, content)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("GET", path, None)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
         self.assertEquals(json.loads(content), channel.json_body)
 
@@ -670,11 +614,9 @@ class RoomMemberStateTestCase(RoomBase):
             "Join us!",
         )
         request, channel = self.make_request("PUT", path, content)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("GET", path, None)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
         self.assertEquals(json.loads(content), channel.json_body)
 
@@ -725,7 +667,6 @@ class RoomJoinRatelimitTestCase(RoomBase):
         # Update the display name for the user.
         path = "/_matrix/client/r0/profile/%s/displayname" % self.user_id
         request, channel = self.make_request("PUT", path, {"displayname": "John Doe"})
-        self.render(request)
         self.assertEquals(channel.code, 200, channel.json_body)
 
         # Check that all the rooms have been sent a profile update into.
@@ -736,7 +677,6 @@ class RoomJoinRatelimitTestCase(RoomBase):
             )
 
             request, channel = self.make_request("GET", path)
-            self.render(request)
             self.assertEquals(channel.code, 200)
 
             self.assertIn("displayname", channel.json_body)
@@ -761,7 +701,6 @@ class RoomJoinRatelimitTestCase(RoomBase):
             # if all of these requests ended up joining the user to a room.
             for i in range(4):
                 request, channel = self.make_request("POST", path % room_id, {})
-                self.render(request)
                 self.assertEquals(channel.code, 200)
 
 
@@ -777,29 +716,23 @@ class RoomMessagesTestCase(RoomBase):
         path = "/rooms/%s/send/m.room.message/mid1" % (urlparse.quote(self.room_id))
         # missing keys or invalid json
         request, channel = self.make_request("PUT", path, b"{}")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", path, b'{"_name":"bo"}')
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", path, b'{"nao')
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request(
             "PUT", path, b'[{"_name":"bo"},{"_name":"jill"}]'
         )
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", path, b"text only")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         request, channel = self.make_request("PUT", path, b"")
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
     def test_rooms_messages_sent(self):
@@ -807,20 +740,17 @@ class RoomMessagesTestCase(RoomBase):
 
         content = b'{"body":"test","msgtype":{"type":"a"}}'
         request, channel = self.make_request("PUT", path, content)
-        self.render(request)
         self.assertEquals(400, channel.code, msg=channel.result["body"])
 
         # custom message types
         content = b'{"body":"test","msgtype":"test.custom.text"}'
         request, channel = self.make_request("PUT", path, content)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
         # m.text message type
         path = "/rooms/%s/send/m.room.message/mid2" % (urlparse.quote(self.room_id))
         content = b'{"body":"test2","msgtype":"m.text"}'
         request, channel = self.make_request("PUT", path, content)
-        self.render(request)
         self.assertEquals(200, channel.code, msg=channel.result["body"])
 
 
@@ -837,7 +767,6 @@ class RoomInitialSyncTestCase(RoomBase):
         request, channel = self.make_request(
             "GET", "/rooms/%s/initialSync" % self.room_id
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         self.assertEquals(self.room_id, channel.json_body["room_id"])
@@ -881,7 +810,6 @@ class RoomMessageListTestCase(RoomBase):
         request, channel = self.make_request(
             "GET", "/rooms/%s/messages?access_token=x&from=%s" % (self.room_id, token)
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         self.assertTrue("start" in channel.json_body)
         self.assertEquals(token, channel.json_body["start"])
@@ -893,7 +821,6 @@ class RoomMessageListTestCase(RoomBase):
         request, channel = self.make_request(
             "GET", "/rooms/%s/messages?access_token=x&from=%s" % (self.room_id, token)
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         self.assertTrue("start" in channel.json_body)
         self.assertEquals(token, channel.json_body["start"])
@@ -933,7 +860,6 @@ class RoomMessageListTestCase(RoomBase):
                 json.dumps({"types": [EventTypes.Message]}),
             ),
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.json_body)
 
         chunk = channel.json_body["chunk"]
@@ -962,7 +888,6 @@ class RoomMessageListTestCase(RoomBase):
                 json.dumps({"types": [EventTypes.Message]}),
             ),
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.json_body)
 
         chunk = channel.json_body["chunk"]
@@ -980,7 +905,6 @@ class RoomMessageListTestCase(RoomBase):
                 json.dumps({"types": [EventTypes.Message]}),
             ),
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.json_body)
 
         chunk = channel.json_body["chunk"]
@@ -1040,7 +964,6 @@ class RoomSearchTestCase(unittest.HomeserverTestCase):
                 }
             },
         )
-        self.render(request)
 
         # Check we get the results we expect -- one search result, of the sent
         # messages
@@ -1074,7 +997,6 @@ class RoomSearchTestCase(unittest.HomeserverTestCase):
                 }
             },
         )
-        self.render(request)
 
         # Check we get the results we expect -- one search result, of the sent
         # messages
@@ -1111,7 +1033,6 @@ class PublicRoomsRestrictedTestCase(unittest.HomeserverTestCase):
 
     def test_restricted_no_auth(self):
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
         self.assertEqual(channel.code, 401, channel.result)
 
     def test_restricted_auth(self):
@@ -1119,7 +1040,6 @@ class PublicRoomsRestrictedTestCase(unittest.HomeserverTestCase):
         tok = self.login("user", "pass")
 
         request, channel = self.make_request("GET", self.url, access_token=tok)
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
 
@@ -1153,7 +1073,6 @@ class PerRoomProfilesForbiddenTestCase(unittest.HomeserverTestCase):
             request_data,
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         self.room_id = self.helper.create_room_as(self.user_id, tok=self.tok)
@@ -1168,7 +1087,6 @@ class PerRoomProfilesForbiddenTestCase(unittest.HomeserverTestCase):
             request_data,
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
         event_id = channel.json_body["event_id"]
 
@@ -1177,7 +1095,6 @@ class PerRoomProfilesForbiddenTestCase(unittest.HomeserverTestCase):
             "/_matrix/client/r0/rooms/%s/event/%s" % (self.room_id, event_id),
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         res_displayname = channel.json_body["content"]["displayname"]
@@ -1212,7 +1129,6 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
             content={"reason": reason},
             access_token=self.second_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         self._check_for_reason(reason)
@@ -1227,7 +1143,6 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
             content={"reason": reason},
             access_token=self.second_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         self._check_for_reason(reason)
@@ -1242,7 +1157,6 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
             content={"reason": reason, "user_id": self.second_user_id},
             access_token=self.second_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         self._check_for_reason(reason)
@@ -1257,7 +1171,6 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
             content={"reason": reason, "user_id": self.second_user_id},
             access_token=self.creator_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         self._check_for_reason(reason)
@@ -1270,7 +1183,6 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
             content={"reason": reason, "user_id": self.second_user_id},
             access_token=self.creator_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         self._check_for_reason(reason)
@@ -1283,7 +1195,6 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
             content={"reason": reason, "user_id": self.second_user_id},
             access_token=self.creator_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         self._check_for_reason(reason)
@@ -1303,7 +1214,6 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
             content={"reason": reason},
             access_token=self.second_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         self._check_for_reason(reason)
@@ -1316,7 +1226,6 @@ class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
             ),
             access_token=self.creator_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         event_content = channel.json_body
@@ -1365,7 +1274,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
             % (self.room_id, event_id, json.dumps(self.FILTER_LABELS)),
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         events_before = channel.json_body["events_before"]
@@ -1396,7 +1304,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
             % (self.room_id, event_id, json.dumps(self.FILTER_NOT_LABELS)),
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         events_before = channel.json_body["events_before"]
@@ -1432,7 +1339,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
             % (self.room_id, event_id, json.dumps(self.FILTER_LABELS_NOT_LABELS)),
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         events_before = channel.json_body["events_before"]
@@ -1460,7 +1366,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/messages?access_token=%s&from=%s&filter=%s"
             % (self.room_id, self.tok, token, json.dumps(self.FILTER_LABELS)),
         )
-        self.render(request)
 
         events = channel.json_body["chunk"]
 
@@ -1478,7 +1383,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/messages?access_token=%s&from=%s&filter=%s"
             % (self.room_id, self.tok, token, json.dumps(self.FILTER_NOT_LABELS)),
         )
-        self.render(request)
 
         events = channel.json_body["chunk"]
 
@@ -1507,7 +1411,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
                 json.dumps(self.FILTER_LABELS_NOT_LABELS),
             ),
         )
-        self.render(request)
 
         events = channel.json_body["chunk"]
 
@@ -1532,7 +1435,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", "/search?access_token=%s" % self.tok, request_data
         )
-        self.render(request)
 
         results = channel.json_body["search_categories"]["room_events"]["results"]
 
@@ -1568,7 +1470,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", "/search?access_token=%s" % self.tok, request_data
         )
-        self.render(request)
 
         results = channel.json_body["search_categories"]["room_events"]["results"]
 
@@ -1616,7 +1517,6 @@ class LabelsTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", "/search?access_token=%s" % self.tok, request_data
         )
-        self.render(request)
 
         results = channel.json_body["search_categories"]["room_events"]["results"]
 
@@ -1741,7 +1641,6 @@ class ContextTestCase(unittest.HomeserverTestCase):
             % (self.room_id, event_id),
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         events_before = channel.json_body["events_before"]
@@ -1806,7 +1705,6 @@ class ContextTestCase(unittest.HomeserverTestCase):
             % (self.room_id, event_id),
             access_token=invited_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         events_before = channel.json_body["events_before"]
@@ -1897,7 +1795,6 @@ class RoomAliasListTestCase(unittest.HomeserverTestCase):
             % (self.room_id,),
             access_token=access_token,
         )
-        self.render(request)
         self.assertEqual(channel.code, expected_code, channel.result)
         res = channel.json_body
         self.assertIsInstance(res, dict)
@@ -1916,7 +1813,6 @@ class RoomAliasListTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url, request_data, access_token=self.room_owner_tok
         )
-        self.render(request)
         self.assertEqual(channel.code, expected_code, channel.result)
 
 
@@ -1947,7 +1843,6 @@ class RoomCanonicalAliasTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "PUT", url, request_data, access_token=self.room_owner_tok
         )
-        self.render(request)
         self.assertEqual(channel.code, expected_code, channel.result)
 
     def _get_canonical_alias(self, expected_code: int = 200) -> JsonDict:
@@ -1957,7 +1852,6 @@ class RoomCanonicalAliasTestCase(unittest.HomeserverTestCase):
             "rooms/%s/state/m.room.canonical_alias" % (self.room_id,),
             access_token=self.room_owner_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, expected_code, channel.result)
         res = channel.json_body
         self.assertIsInstance(res, dict)
@@ -1971,7 +1865,6 @@ class RoomCanonicalAliasTestCase(unittest.HomeserverTestCase):
             json.dumps(content),
             access_token=self.room_owner_tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, expected_code, channel.result)
         res = channel.json_body
         self.assertIsInstance(res, dict)
diff --git a/tests/rest/client/v1/test_typing.py b/tests/rest/client/v1/test_typing.py
index cd58ee7792..bbd30f594b 100644
--- a/tests/rest/client/v1/test_typing.py
+++ b/tests/rest/client/v1/test_typing.py
@@ -99,7 +99,6 @@ class RoomTypingTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
             b'{"typing": true, "timeout": 30000}',
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         self.assertEquals(self.event_source.get_current_key(), 1)
@@ -123,7 +122,6 @@ class RoomTypingTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
             b'{"typing": false}',
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
     def test_typing_timeout(self):
@@ -132,7 +130,6 @@ class RoomTypingTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
             b'{"typing": true, "timeout": 30000}',
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         self.assertEquals(self.event_source.get_current_key(), 1)
@@ -146,7 +143,6 @@ class RoomTypingTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
             b'{"typing": true, "timeout": 30000}',
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         self.assertEquals(self.event_source.get_current_key(), 3)
diff --git a/tests/rest/client/v1/utils.py b/tests/rest/client/v1/utils.py
index afaf9f7b85..b58768675b 100644
--- a/tests/rest/client/v1/utils.py
+++ b/tests/rest/client/v1/utils.py
@@ -23,10 +23,11 @@ from typing import Any, Dict, Optional
 import attr
 
 from twisted.web.resource import Resource
+from twisted.web.server import Site
 
 from synapse.api.constants import Membership
 
-from tests.server import make_request, render
+from tests.server import FakeSite, make_request
 
 
 @attr.s
@@ -36,7 +37,7 @@ class RestHelper:
     """
 
     hs = attr.ib()
-    resource = attr.ib()
+    site = attr.ib(type=Site)
     auth_user_id = attr.ib()
 
     def create_room_as(
@@ -51,10 +52,13 @@ class RestHelper:
         if tok:
             path = path + "?access_token=%s" % tok
 
-        request, channel = make_request(
-            self.hs.get_reactor(), "POST", path, json.dumps(content).encode("utf8")
+        _, channel = make_request(
+            self.hs.get_reactor(),
+            self.site,
+            "POST",
+            path,
+            json.dumps(content).encode("utf8"),
         )
-        render(request, self.resource, self.hs.get_reactor())
 
         assert channel.result["code"] == b"%d" % expect_code, channel.result
         self.auth_user_id = temp_id
@@ -124,12 +128,14 @@ class RestHelper:
         data = {"membership": membership}
         data.update(extra_data)
 
-        request, channel = make_request(
-            self.hs.get_reactor(), "PUT", path, json.dumps(data).encode("utf8")
+        _, channel = make_request(
+            self.hs.get_reactor(),
+            self.site,
+            "PUT",
+            path,
+            json.dumps(data).encode("utf8"),
         )
 
-        render(request, self.resource, self.hs.get_reactor())
-
         assert int(channel.result["code"]) == expect_code, (
             "Expected: %d, got: %d, resp: %r"
             % (expect_code, int(channel.result["code"]), channel.result["body"])
@@ -157,10 +163,13 @@ class RestHelper:
         if tok:
             path = path + "?access_token=%s" % tok
 
-        request, channel = make_request(
-            self.hs.get_reactor(), "PUT", path, json.dumps(content).encode("utf8")
+        _, channel = make_request(
+            self.hs.get_reactor(),
+            self.site,
+            "PUT",
+            path,
+            json.dumps(content).encode("utf8"),
         )
-        render(request, self.resource, self.hs.get_reactor())
 
         assert int(channel.result["code"]) == expect_code, (
             "Expected: %d, got: %d, resp: %r"
@@ -210,9 +219,9 @@ class RestHelper:
         if body is not None:
             content = json.dumps(body).encode("utf8")
 
-        request, channel = make_request(self.hs.get_reactor(), method, path, content)
-
-        render(request, self.resource, self.hs.get_reactor())
+        _, channel = make_request(
+            self.hs.get_reactor(), self.site, method, path, content
+        )
 
         assert int(channel.result["code"]) == expect_code, (
             "Expected: %d, got: %d, resp: %r"
@@ -295,14 +304,15 @@ class RestHelper:
         """
         image_length = len(image_data)
         path = "/_matrix/media/r0/upload?filename=%s" % (filename,)
-        request, channel = make_request(
-            self.hs.get_reactor(), "POST", path, content=image_data, access_token=tok
-        )
-        request.requestHeaders.addRawHeader(
-            b"Content-Length", str(image_length).encode("UTF-8")
+        _, channel = make_request(
+            self.hs.get_reactor(),
+            FakeSite(resource),
+            "POST",
+            path,
+            content=image_data,
+            access_token=tok,
+            custom_headers=[(b"Content-Length", str(image_length))],
         )
-        request.render(resource)
-        self.hs.get_reactor().pump([100])
 
         assert channel.code == expect_code, "Expected: %d, got: %d, resp: %r" % (
             expect_code,
diff --git a/tests/rest/client/v2_alpha/test_account.py b/tests/rest/client/v2_alpha/test_account.py
index 66ac4dbe85..2ac1ecb7d3 100644
--- a/tests/rest/client/v2_alpha/test_account.py
+++ b/tests/rest/client/v2_alpha/test_account.py
@@ -31,6 +31,7 @@ from synapse.rest.client.v2_alpha import account, register
 from synapse.rest.synapse.client.password_reset import PasswordResetSubmitTokenResource
 
 from tests import unittest
+from tests.server import FakeSite, make_request
 from tests.unittest import override_config
 
 
@@ -245,7 +246,6 @@ class PasswordResetTestCase(unittest.HomeserverTestCase):
             b"account/password/email/requestToken",
             {"client_secret": client_secret, "email": email, "send_attempt": 1},
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
         return channel.json_body["sid"]
@@ -255,9 +255,14 @@ class PasswordResetTestCase(unittest.HomeserverTestCase):
         path = link.replace("https://example.com", "")
 
         # Load the password reset confirmation page
-        request, channel = self.make_request("GET", path, shorthand=False)
-        request.render(self.submit_token_resource)
-        self.pump()
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(self.submit_token_resource),
+            "GET",
+            path,
+            shorthand=False,
+        )
+
         self.assertEquals(200, channel.code, channel.result)
 
         # Now POST to the same endpoint, mimicking the same behaviour as clicking the
@@ -271,15 +276,15 @@ class PasswordResetTestCase(unittest.HomeserverTestCase):
                 form_args.append(arg)
 
         # Confirm the password reset
-        request, channel = self.make_request(
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(self.submit_token_resource),
             "POST",
             path,
             content=urlencode(form_args).encode("utf8"),
             shorthand=False,
             content_is_form=True,
         )
-        request.render(self.submit_token_resource)
-        self.pump()
         self.assertEquals(200, channel.code, channel.result)
 
     def _get_link_from_email(self):
@@ -319,7 +324,6 @@ class PasswordResetTestCase(unittest.HomeserverTestCase):
                 },
             },
         )
-        self.render(request)
         self.assertEquals(expected_code, channel.code, channel.result)
 
 
@@ -349,7 +353,6 @@ class DeactivateTestCase(unittest.HomeserverTestCase):
 
         # Check that this access token has been invalidated.
         request, channel = self.make_request("GET", "account/whoami")
-        self.render(request)
         self.assertEqual(request.code, 401)
 
     def test_pending_invites(self):
@@ -407,7 +410,6 @@ class DeactivateTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", "account/deactivate", request_data, access_token=tok
         )
-        self.render(request)
         self.assertEqual(request.code, 200)
 
 
@@ -542,7 +544,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
             },
             access_token=self.user_id_tok,
         )
-        self.render(request)
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
 
@@ -550,7 +551,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url_3pid, access_token=self.user_id_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertFalse(channel.json_body["threepids"])
@@ -575,14 +575,12 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
             {"medium": "email", "address": self.email},
             access_token=self.user_id_tok,
         )
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Get user
         request, channel = self.make_request(
             "GET", self.url_3pid, access_token=self.user_id_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertFalse(channel.json_body["threepids"])
@@ -609,7 +607,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
             {"medium": "email", "address": self.email},
             access_token=self.user_id_tok,
         )
-        self.render(request)
 
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
@@ -618,7 +615,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url_3pid, access_token=self.user_id_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("email", channel.json_body["threepids"][0]["medium"])
@@ -647,7 +643,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
             },
             access_token=self.user_id_tok,
         )
-        self.render(request)
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.THREEPID_AUTH_FAILED, channel.json_body["errcode"])
 
@@ -655,7 +650,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url_3pid, access_token=self.user_id_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertFalse(channel.json_body["threepids"])
@@ -682,7 +676,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
             },
             access_token=self.user_id_tok,
         )
-        self.render(request)
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(Codes.THREEPID_AUTH_FAILED, channel.json_body["errcode"])
 
@@ -690,7 +683,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url_3pid, access_token=self.user_id_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertFalse(channel.json_body["threepids"])
@@ -795,7 +787,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", b"account/3pid/email/requestToken", body,
         )
-        self.render(request)
         self.assertEquals(expect_code, channel.code, channel.result)
 
         return channel.json_body.get("sid")
@@ -808,7 +799,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
             b"account/3pid/email/requestToken",
             {"client_secret": client_secret, "email": email, "send_attempt": 1},
         )
-        self.render(request)
         self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual(expected_errcode, channel.json_body["errcode"])
         self.assertEqual(expected_error, channel.json_body["error"])
@@ -818,7 +808,6 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
         path = link.replace("https://example.com", "")
 
         request, channel = self.make_request("GET", path, shorthand=False)
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
     def _get_link_from_email(self):
@@ -867,14 +856,12 @@ class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
             access_token=self.user_id_tok,
         )
 
-        self.render(request)
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
 
         # Get user
         request, channel = self.make_request(
             "GET", self.url_3pid, access_token=self.user_id_tok,
         )
-        self.render(request)
 
         self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
         self.assertEqual("email", channel.json_body["threepids"][0]["medium"])
diff --git a/tests/rest/client/v2_alpha/test_auth.py b/tests/rest/client/v2_alpha/test_auth.py
index 86184f0d2e..f684c37db5 100644
--- a/tests/rest/client/v2_alpha/test_auth.py
+++ b/tests/rest/client/v2_alpha/test_auth.py
@@ -72,7 +72,6 @@ class FallbackAuthTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", "register", body
         )  # type: SynapseRequest, FakeChannel
-        self.render(request)
 
         self.assertEqual(request.code, expected_response)
         return channel
@@ -87,7 +86,6 @@ class FallbackAuthTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "auth/m.login.recaptcha/fallback/web?session=" + session
         )  # type: SynapseRequest, FakeChannel
-        self.render(request)
         self.assertEqual(request.code, 200)
 
         request, channel = self.make_request(
@@ -96,7 +94,6 @@ class FallbackAuthTests(unittest.HomeserverTestCase):
             + post_session
             + "&g-recaptcha-response=a",
         )
-        self.render(request)
         self.assertEqual(request.code, expected_post_response)
 
         # The recaptcha handler is called with the response given
@@ -177,7 +174,6 @@ class UIAuthTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "devices", access_token=self.user_tok,
         )  # type: SynapseRequest, FakeChannel
-        self.render(request)
 
         # Get the ID of the device.
         self.assertEqual(request.code, 200)
@@ -190,7 +186,6 @@ class UIAuthTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "DELETE", "devices/" + device, body, access_token=self.user_tok
         )  # type: SynapseRequest, FakeChannel
-        self.render(request)
 
         # Ensure the response is sane.
         self.assertEqual(request.code, expected_response)
@@ -204,7 +199,6 @@ class UIAuthTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", "delete_devices", body, access_token=self.user_tok,
         )  # type: SynapseRequest, FakeChannel
-        self.render(request)
 
         # Ensure the response is sane.
         self.assertEqual(request.code, expected_response)
diff --git a/tests/rest/client/v2_alpha/test_capabilities.py b/tests/rest/client/v2_alpha/test_capabilities.py
index b9e01c9418..767e126875 100644
--- a/tests/rest/client/v2_alpha/test_capabilities.py
+++ b/tests/rest/client/v2_alpha/test_capabilities.py
@@ -37,7 +37,6 @@ class CapabilitiesTestCase(unittest.HomeserverTestCase):
 
     def test_check_auth_required(self):
         request, channel = self.make_request("GET", self.url)
-        self.render(request)
 
         self.assertEqual(channel.code, 401)
 
@@ -46,7 +45,6 @@ class CapabilitiesTestCase(unittest.HomeserverTestCase):
         access_token = self.login("user", "pass")
 
         request, channel = self.make_request("GET", self.url, access_token=access_token)
-        self.render(request)
         capabilities = channel.json_body["capabilities"]
 
         self.assertEqual(channel.code, 200)
@@ -65,7 +63,6 @@ class CapabilitiesTestCase(unittest.HomeserverTestCase):
         access_token = self.login(user, password)
 
         request, channel = self.make_request("GET", self.url, access_token=access_token)
-        self.render(request)
         capabilities = channel.json_body["capabilities"]
 
         self.assertEqual(channel.code, 200)
@@ -74,7 +71,6 @@ class CapabilitiesTestCase(unittest.HomeserverTestCase):
         self.assertTrue(capabilities["m.change_password"]["enabled"])
         self.get_success(self.store.user_set_password_hash(user, None))
         request, channel = self.make_request("GET", self.url, access_token=access_token)
-        self.render(request)
         capabilities = channel.json_body["capabilities"]
 
         self.assertEqual(channel.code, 200)
diff --git a/tests/rest/client/v2_alpha/test_filter.py b/tests/rest/client/v2_alpha/test_filter.py
index de00350580..231d5aefea 100644
--- a/tests/rest/client/v2_alpha/test_filter.py
+++ b/tests/rest/client/v2_alpha/test_filter.py
@@ -41,7 +41,6 @@ class FilterTestCase(unittest.HomeserverTestCase):
             "/_matrix/client/r0/user/%s/filter" % (self.user_id),
             self.EXAMPLE_FILTER_JSON,
         )
-        self.render(request)
 
         self.assertEqual(channel.result["code"], b"200")
         self.assertEqual(channel.json_body, {"filter_id": "0"})
@@ -55,7 +54,6 @@ class FilterTestCase(unittest.HomeserverTestCase):
             "/_matrix/client/r0/user/%s/filter" % ("@watermelon:test"),
             self.EXAMPLE_FILTER_JSON,
         )
-        self.render(request)
 
         self.assertEqual(channel.result["code"], b"403")
         self.assertEquals(channel.json_body["errcode"], Codes.FORBIDDEN)
@@ -68,7 +66,6 @@ class FilterTestCase(unittest.HomeserverTestCase):
             "/_matrix/client/r0/user/%s/filter" % (self.user_id),
             self.EXAMPLE_FILTER_JSON,
         )
-        self.render(request)
 
         self.hs.is_mine = _is_mine
         self.assertEqual(channel.result["code"], b"403")
@@ -85,7 +82,6 @@ class FilterTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/user/%s/filter/%s" % (self.user_id, filter_id)
         )
-        self.render(request)
 
         self.assertEqual(channel.result["code"], b"200")
         self.assertEquals(channel.json_body, self.EXAMPLE_FILTER)
@@ -94,7 +90,6 @@ class FilterTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/user/%s/filter/12382148321" % (self.user_id)
         )
-        self.render(request)
 
         self.assertEqual(channel.result["code"], b"404")
         self.assertEquals(channel.json_body["errcode"], Codes.NOT_FOUND)
@@ -105,7 +100,6 @@ class FilterTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/user/%s/filter/foobar" % (self.user_id)
         )
-        self.render(request)
 
         self.assertEqual(channel.result["code"], b"400")
 
@@ -114,6 +108,5 @@ class FilterTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/user/%s/filter/" % (self.user_id)
         )
-        self.render(request)
 
         self.assertEqual(channel.result["code"], b"400")
diff --git a/tests/rest/client/v2_alpha/test_password_policy.py b/tests/rest/client/v2_alpha/test_password_policy.py
index c57072f50c..ee86b94917 100644
--- a/tests/rest/client/v2_alpha/test_password_policy.py
+++ b/tests/rest/client/v2_alpha/test_password_policy.py
@@ -73,7 +73,6 @@ class PasswordPolicyTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/password_policy"
         )
-        self.render(request)
 
         self.assertEqual(channel.code, 200, channel.result)
         self.assertEqual(
@@ -91,7 +90,6 @@ class PasswordPolicyTestCase(unittest.HomeserverTestCase):
     def test_password_too_short(self):
         request_data = json.dumps({"username": "kermit", "password": "shorty"})
         request, channel = self.make_request("POST", self.register_url, request_data)
-        self.render(request)
 
         self.assertEqual(channel.code, 400, channel.result)
         self.assertEqual(
@@ -101,7 +99,6 @@ class PasswordPolicyTestCase(unittest.HomeserverTestCase):
     def test_password_no_digit(self):
         request_data = json.dumps({"username": "kermit", "password": "longerpassword"})
         request, channel = self.make_request("POST", self.register_url, request_data)
-        self.render(request)
 
         self.assertEqual(channel.code, 400, channel.result)
         self.assertEqual(
@@ -111,7 +108,6 @@ class PasswordPolicyTestCase(unittest.HomeserverTestCase):
     def test_password_no_symbol(self):
         request_data = json.dumps({"username": "kermit", "password": "l0ngerpassword"})
         request, channel = self.make_request("POST", self.register_url, request_data)
-        self.render(request)
 
         self.assertEqual(channel.code, 400, channel.result)
         self.assertEqual(
@@ -121,7 +117,6 @@ class PasswordPolicyTestCase(unittest.HomeserverTestCase):
     def test_password_no_uppercase(self):
         request_data = json.dumps({"username": "kermit", "password": "l0ngerpassword!"})
         request, channel = self.make_request("POST", self.register_url, request_data)
-        self.render(request)
 
         self.assertEqual(channel.code, 400, channel.result)
         self.assertEqual(
@@ -131,7 +126,6 @@ class PasswordPolicyTestCase(unittest.HomeserverTestCase):
     def test_password_no_lowercase(self):
         request_data = json.dumps({"username": "kermit", "password": "L0NGERPASSWORD!"})
         request, channel = self.make_request("POST", self.register_url, request_data)
-        self.render(request)
 
         self.assertEqual(channel.code, 400, channel.result)
         self.assertEqual(
@@ -141,7 +135,6 @@ class PasswordPolicyTestCase(unittest.HomeserverTestCase):
     def test_password_compliant(self):
         request_data = json.dumps({"username": "kermit", "password": "L0ngerpassword!"})
         request, channel = self.make_request("POST", self.register_url, request_data)
-        self.render(request)
 
         # Getting a 401 here means the password has passed validation and the server has
         # responded with a list of registration flows.
@@ -173,7 +166,6 @@ class PasswordPolicyTestCase(unittest.HomeserverTestCase):
             request_data,
             access_token=tok,
         )
-        self.render(request)
 
         self.assertEqual(channel.code, 400, channel.result)
         self.assertEqual(channel.json_body["errcode"], Codes.PASSWORD_NO_DIGIT)
diff --git a/tests/rest/client/v2_alpha/test_register.py b/tests/rest/client/v2_alpha/test_register.py
index 98c3887bbf..88923fcea4 100644
--- a/tests/rest/client/v2_alpha/test_register.py
+++ b/tests/rest/client/v2_alpha/test_register.py
@@ -64,7 +64,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
         )
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"200", channel.result)
         det_data = {"user_id": user_id, "home_server": self.hs.hostname}
@@ -76,14 +75,12 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", self.url + b"?access_token=i_am_an_app_service", request_data
         )
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"401", channel.result)
 
     def test_POST_bad_password(self):
         request_data = json.dumps({"username": "kermit", "password": 666})
         request, channel = self.make_request(b"POST", self.url, request_data)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"400", channel.result)
         self.assertEquals(channel.json_body["error"], "Invalid password")
@@ -91,7 +88,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
     def test_POST_bad_username(self):
         request_data = json.dumps({"username": 777, "password": "monkey"})
         request, channel = self.make_request(b"POST", self.url, request_data)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"400", channel.result)
         self.assertEquals(channel.json_body["error"], "Invalid username")
@@ -107,7 +103,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         }
         request_data = json.dumps(params)
         request, channel = self.make_request(b"POST", self.url, request_data)
-        self.render(request)
 
         det_data = {
             "user_id": user_id,
@@ -123,7 +118,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         self.auth_result = (None, {"username": "kermit", "password": "monkey"}, None)
 
         request, channel = self.make_request(b"POST", self.url, request_data)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"403", channel.result)
         self.assertEquals(channel.json_body["error"], "Registration has been disabled")
@@ -133,7 +127,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         self.hs.config.allow_guest_access = True
 
         request, channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
-        self.render(request)
 
         det_data = {"home_server": self.hs.hostname, "device_id": "guest_device"}
         self.assertEquals(channel.result["code"], b"200", channel.result)
@@ -143,7 +136,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         self.hs.config.allow_guest_access = False
 
         request, channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"403", channel.result)
         self.assertEquals(channel.json_body["error"], "Guest access is disabled")
@@ -153,7 +145,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         for i in range(0, 6):
             url = self.url + b"?kind=guest"
             request, channel = self.make_request(b"POST", url, b"{}")
-            self.render(request)
 
             if i == 5:
                 self.assertEquals(channel.result["code"], b"429", channel.result)
@@ -164,7 +155,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
 
         request, channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
@@ -179,7 +169,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
             }
             request_data = json.dumps(params)
             request, channel = self.make_request(b"POST", self.url, request_data)
-            self.render(request)
 
             if i == 5:
                 self.assertEquals(channel.result["code"], b"429", channel.result)
@@ -190,13 +179,11 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
         self.reactor.advance(retry_after_ms / 1000.0 + 1.0)
 
         request, channel = self.make_request(b"POST", self.url + b"?kind=guest", b"{}")
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
     def test_advertised_flows(self):
         request, channel = self.make_request(b"POST", self.url, b"{}")
-        self.render(request)
         self.assertEquals(channel.result["code"], b"401", channel.result)
         flows = channel.json_body["flows"]
 
@@ -220,7 +207,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
     )
     def test_advertised_flows_captcha_and_terms_and_3pids(self):
         request, channel = self.make_request(b"POST", self.url, b"{}")
-        self.render(request)
         self.assertEquals(channel.result["code"], b"401", channel.result)
         flows = channel.json_body["flows"]
 
@@ -253,7 +239,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
     )
     def test_advertised_flows_no_msisdn_email_required(self):
         request, channel = self.make_request(b"POST", self.url, b"{}")
-        self.render(request)
         self.assertEquals(channel.result["code"], b"401", channel.result)
         flows = channel.json_body["flows"]
 
@@ -298,7 +283,6 @@ class RegisterRestServletTestCase(unittest.HomeserverTestCase):
             b"register/email/requestToken",
             {"client_secret": "foobar", "email": email, "send_attempt": 1},
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.result)
 
         self.assertIsNotNone(channel.json_body.get("sid"))
@@ -334,14 +318,12 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
         # The specific endpoint doesn't matter, all we need is an authenticated
         # endpoint.
         request, channel = self.make_request(b"GET", "/sync", access_token=tok)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         self.reactor.advance(datetime.timedelta(weeks=1).total_seconds())
 
         request, channel = self.make_request(b"GET", "/sync", access_token=tok)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"403", channel.result)
         self.assertEquals(
@@ -366,13 +348,11 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", url, request_data, access_token=admin_tok
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         # The specific endpoint doesn't matter, all we need is an authenticated
         # endpoint.
         request, channel = self.make_request(b"GET", "/sync", access_token=tok)
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
     def test_manual_expire(self):
@@ -392,13 +372,11 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", url, request_data, access_token=admin_tok
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         # The specific endpoint doesn't matter, all we need is an authenticated
         # endpoint.
         request, channel = self.make_request(b"GET", "/sync", access_token=tok)
-        self.render(request)
         self.assertEquals(channel.result["code"], b"403", channel.result)
         self.assertEquals(
             channel.json_body["errcode"], Codes.EXPIRED_ACCOUNT, channel.result
@@ -421,12 +399,10 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             b"POST", url, request_data, access_token=admin_tok
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         # Try to log the user out
         request, channel = self.make_request(b"POST", "/logout", access_token=tok)
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         # Log the user in again (allowed for expired accounts)
@@ -434,7 +410,6 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
 
         # Try to log out all of the user's sessions
         request, channel = self.make_request(b"POST", "/logout/all", access_token=tok)
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
 
@@ -509,7 +484,6 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
         renewal_token = self.get_success(self.store.get_renewal_token_for_user(user_id))
         url = "/_matrix/client/unstable/account_validity/renew?token=%s" % renewal_token
         request, channel = self.make_request(b"GET", url)
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         # Check that we're getting HTML back.
@@ -530,7 +504,6 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
         # succeed.
         self.reactor.advance(datetime.timedelta(days=3).total_seconds())
         request, channel = self.make_request(b"GET", "/sync", access_token=tok)
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
     def test_renewal_invalid_token(self):
@@ -538,7 +511,6 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
         # expected, i.e. that it responds with 404 Not Found and the correct HTML.
         url = "/_matrix/client/unstable/account_validity/renew?token=123"
         request, channel = self.make_request(b"GET", url)
-        self.render(request)
         self.assertEquals(channel.result["code"], b"404", channel.result)
 
         # Check that we're getting HTML back.
@@ -564,7 +536,6 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
             "/_matrix/client/unstable/account_validity/send_mail",
             access_token=tok,
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         self.assertEqual(len(self.email_attempts), 1)
@@ -587,7 +558,6 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "POST", "account/deactivate", request_data, access_token=tok
         )
-        self.render(request)
         self.assertEqual(request.code, 200)
 
         self.reactor.advance(datetime.timedelta(days=8).total_seconds())
@@ -641,7 +611,6 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
             "/_matrix/client/unstable/account_validity/send_mail",
             access_token=tok,
         )
-        self.render(request)
         self.assertEquals(channel.result["code"], b"200", channel.result)
 
         self.assertEqual(len(self.email_attempts), 1)
diff --git a/tests/rest/client/v2_alpha/test_relations.py b/tests/rest/client/v2_alpha/test_relations.py
index 99c9f4e928..6cd4eb6624 100644
--- a/tests/rest/client/v2_alpha/test_relations.py
+++ b/tests/rest/client/v2_alpha/test_relations.py
@@ -65,7 +65,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/event/%s" % (self.room, event_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         self.assert_dict(
@@ -114,7 +113,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             % (self.room, self.parent_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         # We expect to get back a single pagination result, which is the full
@@ -160,7 +158,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
                 % (self.room, self.parent_id, from_token),
                 access_token=self.user_token,
             )
-            self.render(request)
             self.assertEquals(200, channel.code, channel.json_body)
 
             found_event_ids.extend(e["event_id"] for e in channel.json_body["chunk"])
@@ -219,7 +216,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
                 % (self.room, self.parent_id, from_token),
                 access_token=self.user_token,
             )
-            self.render(request)
             self.assertEquals(200, channel.code, channel.json_body)
 
             self.assertEqual(len(channel.json_body["chunk"]), 1, channel.json_body)
@@ -296,7 +292,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
                 ),
                 access_token=self.user_token,
             )
-            self.render(request)
             self.assertEquals(200, channel.code, channel.json_body)
 
             self.assertEqual(len(channel.json_body["chunk"]), 1, channel.json_body)
@@ -336,7 +331,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             % (self.room, self.parent_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         self.assertEquals(
@@ -369,7 +363,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             access_token=self.user_token,
             content={},
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         request, channel = self.make_request(
@@ -378,7 +371,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             % (self.room, self.parent_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         self.assertEquals(
@@ -396,7 +388,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             % (self.room, self.parent_id, RelationTypes.REPLACE),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(400, channel.code, channel.json_body)
 
     def test_aggregation_get_event(self):
@@ -428,7 +419,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/event/%s" % (self.room, self.parent_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         self.assertEquals(
@@ -465,7 +455,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/event/%s" % (self.room, self.parent_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         self.assertEquals(channel.json_body["content"], new_body)
@@ -523,7 +512,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             "/rooms/%s/event/%s" % (self.room, self.parent_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         self.assertEquals(channel.json_body["content"], new_body)
@@ -567,7 +555,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             % (self.room, original_event_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         self.assertIn("chunk", channel.json_body)
@@ -581,7 +568,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             access_token=self.user_token,
             content="{}",
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         # Try to check for remaining m.replace relations
@@ -591,7 +577,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             % (self.room, original_event_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         # Check that no relations are returned
@@ -624,7 +609,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             access_token=self.user_token,
             content="{}",
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         # Check that aggregations returns zero
@@ -634,7 +618,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             % (self.room, original_event_id),
             access_token=self.user_token,
         )
-        self.render(request)
         self.assertEquals(200, channel.code, channel.json_body)
 
         self.assertIn("chunk", channel.json_body)
@@ -680,7 +663,6 @@ class RelationsTestCase(unittest.HomeserverTestCase):
             json.dumps(content).encode("utf-8"),
             access_token=access_token,
         )
-        self.render(request)
         return channel
 
     def _create_user(self, localpart):
diff --git a/tests/rest/client/v2_alpha/test_shared_rooms.py b/tests/rest/client/v2_alpha/test_shared_rooms.py
index 5ae72fd008..562a9c1ba4 100644
--- a/tests/rest/client/v2_alpha/test_shared_rooms.py
+++ b/tests/rest/client/v2_alpha/test_shared_rooms.py
@@ -47,7 +47,6 @@ class UserSharedRoomsTest(unittest.HomeserverTestCase):
             % other_user,
             access_token=token,
         )
-        self.render(request)
         return request, channel
 
     def test_shared_room_list_public(self):
diff --git a/tests/rest/client/v2_alpha/test_sync.py b/tests/rest/client/v2_alpha/test_sync.py
index a31e44c97e..31ac0fccb8 100644
--- a/tests/rest/client/v2_alpha/test_sync.py
+++ b/tests/rest/client/v2_alpha/test_sync.py
@@ -36,7 +36,6 @@ class FilterTestCase(unittest.HomeserverTestCase):
 
     def test_sync_argless(self):
         request, channel = self.make_request("GET", "/sync")
-        self.render(request)
 
         self.assertEqual(channel.code, 200)
         self.assertTrue(
@@ -57,7 +56,6 @@ class FilterTestCase(unittest.HomeserverTestCase):
         self.hs.config.use_presence = False
 
         request, channel = self.make_request("GET", "/sync")
-        self.render(request)
 
         self.assertEqual(channel.code, 200)
         self.assertTrue(
@@ -199,7 +197,6 @@ class SyncFilterTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/sync?filter=%s" % sync_filter, access_token=tok
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         return channel.json_body["rooms"]["join"][room_id]["timeline"]["events"]
@@ -253,13 +250,11 @@ class SyncTypingTests(unittest.HomeserverTestCase):
             typing_url % (room, other_user_id, other_access_token),
             b'{"typing": true, "timeout": 30000}',
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         request, channel = self.make_request(
             "GET", "/sync?access_token=%s" % (access_token,)
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         next_batch = channel.json_body["next_batch"]
 
@@ -269,7 +264,6 @@ class SyncTypingTests(unittest.HomeserverTestCase):
             typing_url % (room, other_user_id, other_access_token),
             b'{"typing": false}',
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         # Start typing.
@@ -278,14 +272,12 @@ class SyncTypingTests(unittest.HomeserverTestCase):
             typing_url % (room, other_user_id, other_access_token),
             b'{"typing": true, "timeout": 30000}',
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
 
         # Should return immediately
         request, channel = self.make_request(
             "GET", sync_url % (access_token, next_batch)
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         next_batch = channel.json_body["next_batch"]
 
@@ -300,7 +292,6 @@ class SyncTypingTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", sync_url % (access_token, next_batch)
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         next_batch = channel.json_body["next_batch"]
 
@@ -311,7 +302,6 @@ class SyncTypingTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", sync_url % (access_token, next_batch)
         )
-        self.render(request)
         self.assertEquals(200, channel.code)
         next_batch = channel.json_body["next_batch"]
 
@@ -320,10 +310,8 @@ class SyncTypingTests(unittest.HomeserverTestCase):
         typing._reset()
 
         # Now it SHOULD fail as it never completes!
-        request, channel = self.make_request(
-            "GET", sync_url % (access_token, next_batch)
-        )
-        self.assertRaises(TimedOutException, self.render, request)
+        with self.assertRaises(TimedOutException):
+            self.make_request("GET", sync_url % (access_token, next_batch))
 
 
 class UnreadMessagesTestCase(unittest.HomeserverTestCase):
@@ -401,7 +389,6 @@ class UnreadMessagesTestCase(unittest.HomeserverTestCase):
             body,
             access_token=self.tok,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.json_body)
 
         # Check that the unread counter is back to 0.
@@ -466,7 +453,6 @@ class UnreadMessagesTestCase(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", self.url % self.next_batch, access_token=self.tok,
         )
-        self.render(request)
 
         self.assertEqual(channel.code, 200, channel.json_body)
 
diff --git a/tests/rest/key/v2/test_remote_key_resource.py b/tests/rest/key/v2/test_remote_key_resource.py
index 6850c666be..fbcf8d5b86 100644
--- a/tests/rest/key/v2/test_remote_key_resource.py
+++ b/tests/rest/key/v2/test_remote_key_resource.py
@@ -32,7 +32,7 @@ from synapse.util.httpresourcetree import create_resource_tree
 from synapse.util.stringutils import random_string
 
 from tests import unittest
-from tests.server import FakeChannel, wait_until_result
+from tests.server import FakeChannel
 from tests.utils import default_config
 
 
@@ -41,7 +41,7 @@ class BaseRemoteKeyResourceTestCase(unittest.HomeserverTestCase):
         self.http_client = Mock()
         return self.setup_test_homeserver(http_client=self.http_client)
 
-    def create_test_json_resource(self):
+    def create_test_resource(self):
         return create_resource_tree(
             {"/_matrix/key/v2": KeyApiV2Resource(self.hs)}, root_resource=NoResource()
         )
@@ -94,7 +94,7 @@ class RemoteKeyResourceTestCase(BaseRemoteKeyResourceTestCase):
             % (server_name.encode("utf-8"), key_id.encode("utf-8")),
             b"1.1",
         )
-        wait_until_result(self.reactor, req)
+        channel.await_result()
         self.assertEqual(channel.code, 200)
         resp = channel.json_body
         return resp
@@ -190,7 +190,7 @@ class EndToEndPerspectivesTests(BaseRemoteKeyResourceTestCase):
             req.requestReceived(
                 b"POST", path.encode("utf-8"), b"1.1",
             )
-            wait_until_result(self.reactor, req)
+            channel.await_result()
             self.assertEqual(channel.code, 200)
             resp = channel.json_body
             return resp
diff --git a/tests/rest/media/v1/test_media_storage.py b/tests/rest/media/v1/test_media_storage.py
index 5f897d49cf..2a3b2a8f27 100644
--- a/tests/rest/media/v1/test_media_storage.py
+++ b/tests/rest/media/v1/test_media_storage.py
@@ -36,6 +36,7 @@ from synapse.rest.media.v1.media_storage import MediaStorage
 from synapse.rest.media.v1.storage_provider import FileStorageProviderBackend
 
 from tests import unittest
+from tests.server import FakeSite, make_request
 
 
 class MediaStorageTests(unittest.HomeserverTestCase):
@@ -227,8 +228,14 @@ class MediaRepoTests(unittest.HomeserverTestCase):
 
     def _req(self, content_disposition):
 
-        request, channel = self.make_request("GET", self.media_id, shorthand=False)
-        request.render(self.download_resource)
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(self.download_resource),
+            "GET",
+            self.media_id,
+            shorthand=False,
+            await_result=False,
+        )
         self.pump()
 
         # We've made one fetch, to example.com, using the media URL, and asking
@@ -317,10 +324,14 @@ class MediaRepoTests(unittest.HomeserverTestCase):
 
     def _test_thumbnail(self, method, expected_body, expected_found):
         params = "?width=32&height=32&method=" + method
-        request, channel = self.make_request(
-            "GET", self.media_id + params, shorthand=False
+        request, channel = make_request(
+            self.reactor,
+            FakeSite(self.thumbnail_resource),
+            "GET",
+            self.media_id + params,
+            shorthand=False,
+            await_result=False,
         )
-        request.render(self.thumbnail_resource)
         self.pump()
 
         headers = {
@@ -348,7 +359,6 @@ class MediaRepoTests(unittest.HomeserverTestCase):
                 channel.json_body,
                 {
                     "errcode": "M_NOT_FOUND",
-                    "error": "Not found [b'example.com', b'12345?width=32&height=32&method=%s']"
-                    % method,
+                    "error": "Not found [b'example.com', b'12345']",
                 },
             )
diff --git a/tests/rest/media/v1/test_url_preview.py b/tests/rest/media/v1/test_url_preview.py
index c00a7b9114..ccdc8c2ecf 100644
--- a/tests/rest/media/v1/test_url_preview.py
+++ b/tests/rest/media/v1/test_url_preview.py
@@ -133,13 +133,18 @@ class URLPreviewTests(unittest.HomeserverTestCase):
 
         self.reactor.nameResolver = Resolver()
 
+    def create_test_resource(self):
+        return self.hs.get_media_repository_resource()
+
     def test_cache_returns_correct_type(self):
         self.lookups["matrix.org"] = [(IPv4Address, "10.1.2.3")]
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://matrix.org", shorthand=False
+            "GET",
+            "preview_url?url=http://matrix.org",
+            shorthand=False,
+            await_result=False,
         )
-        request.render(self.preview_url)
         self.pump()
 
         client = self.reactor.tcpClients[0][2].buildProtocol(None)
@@ -160,10 +165,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
 
         # Check the cache returns the correct response
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://matrix.org", shorthand=False
+            "GET", "preview_url?url=http://matrix.org", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
 
         # Check the cache response has the same content
         self.assertEqual(channel.code, 200)
@@ -178,10 +181,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
 
         # Check the database cache returns the correct response
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://matrix.org", shorthand=False
+            "GET", "preview_url?url=http://matrix.org", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
 
         # Check the cache response has the same content
         self.assertEqual(channel.code, 200)
@@ -201,9 +202,11 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         )
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://matrix.org", shorthand=False
+            "GET",
+            "preview_url?url=http://matrix.org",
+            shorthand=False,
+            await_result=False,
         )
-        request.render(self.preview_url)
         self.pump()
 
         client = self.reactor.tcpClients[0][2].buildProtocol(None)
@@ -234,9 +237,11 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         )
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://matrix.org", shorthand=False
+            "GET",
+            "preview_url?url=http://matrix.org",
+            shorthand=False,
+            await_result=False,
         )
-        request.render(self.preview_url)
         self.pump()
 
         client = self.reactor.tcpClients[0][2].buildProtocol(None)
@@ -267,9 +272,11 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         )
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://matrix.org", shorthand=False
+            "GET",
+            "preview_url?url=http://matrix.org",
+            shorthand=False,
+            await_result=False,
         )
-        request.render(self.preview_url)
         self.pump()
 
         client = self.reactor.tcpClients[0][2].buildProtocol(None)
@@ -298,9 +305,11 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         self.lookups["example.com"] = [(IPv4Address, "10.1.2.3")]
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://example.com", shorthand=False
+            "GET",
+            "preview_url?url=http://example.com",
+            shorthand=False,
+            await_result=False,
         )
-        request.render(self.preview_url)
         self.pump()
 
         client = self.reactor.tcpClients[0][2].buildProtocol(None)
@@ -326,10 +335,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         self.lookups["example.com"] = [(IPv4Address, "192.168.1.1")]
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://example.com", shorthand=False
+            "GET", "preview_url?url=http://example.com", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
 
         # No requests made.
         self.assertEqual(len(self.reactor.tcpClients), 0)
@@ -349,10 +356,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         self.lookups["example.com"] = [(IPv4Address, "1.1.1.2")]
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://example.com", shorthand=False
+            "GET", "preview_url?url=http://example.com", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
 
         self.assertEqual(channel.code, 502)
         self.assertEqual(
@@ -368,10 +373,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         Blacklisted IP addresses, accessed directly, are not spidered.
         """
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://192.168.1.1", shorthand=False
+            "GET", "preview_url?url=http://192.168.1.1", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
 
         # No requests made.
         self.assertEqual(len(self.reactor.tcpClients), 0)
@@ -389,10 +392,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         Blacklisted IP ranges, accessed directly, are not spidered.
         """
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://1.1.1.2", shorthand=False
+            "GET", "preview_url?url=http://1.1.1.2", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
 
         self.assertEqual(channel.code, 403)
         self.assertEqual(
@@ -411,9 +412,11 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         self.lookups["example.com"] = [(IPv4Address, "1.1.1.1")]
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://example.com", shorthand=False
+            "GET",
+            "preview_url?url=http://example.com",
+            shorthand=False,
+            await_result=False,
         )
-        request.render(self.preview_url)
         self.pump()
 
         client = self.reactor.tcpClients[0][2].buildProtocol(None)
@@ -446,10 +449,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         ]
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://example.com", shorthand=False
+            "GET", "preview_url?url=http://example.com", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
         self.assertEqual(channel.code, 502)
         self.assertEqual(
             channel.json_body,
@@ -468,10 +469,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         ]
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://example.com", shorthand=False
+            "GET", "preview_url?url=http://example.com", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
 
         # No requests made.
         self.assertEqual(len(self.reactor.tcpClients), 0)
@@ -491,10 +490,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         self.lookups["example.com"] = [(IPv6Address, "2001:800::1")]
 
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://example.com", shorthand=False
+            "GET", "preview_url?url=http://example.com", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
 
         self.assertEqual(channel.code, 502)
         self.assertEqual(
@@ -510,10 +507,8 @@ class URLPreviewTests(unittest.HomeserverTestCase):
         OPTIONS returns the OPTIONS.
         """
         request, channel = self.make_request(
-            "OPTIONS", "url_preview?url=http://example.com", shorthand=False
+            "OPTIONS", "preview_url?url=http://example.com", shorthand=False
         )
-        request.render(self.preview_url)
-        self.pump()
         self.assertEqual(channel.code, 200)
         self.assertEqual(channel.json_body, {})
 
@@ -525,9 +520,11 @@ class URLPreviewTests(unittest.HomeserverTestCase):
 
         # Build and make a request to the server
         request, channel = self.make_request(
-            "GET", "url_preview?url=http://example.com", shorthand=False
+            "GET",
+            "preview_url?url=http://example.com",
+            shorthand=False,
+            await_result=False,
         )
-        request.render(self.preview_url)
         self.pump()
 
         # Extract Synapse's tcp client
@@ -598,10 +595,10 @@ class URLPreviewTests(unittest.HomeserverTestCase):
 
             request, channel = self.make_request(
                 "GET",
-                "url_preview?url=http://twitter.com/matrixdotorg/status/12345",
+                "preview_url?url=http://twitter.com/matrixdotorg/status/12345",
                 shorthand=False,
+                await_result=False,
             )
-            request.render(self.preview_url)
             self.pump()
 
             client = self.reactor.tcpClients[0][2].buildProtocol(None)
@@ -663,10 +660,10 @@ class URLPreviewTests(unittest.HomeserverTestCase):
 
             request, channel = self.make_request(
                 "GET",
-                "url_preview?url=http://twitter.com/matrixdotorg/status/12345",
+                "preview_url?url=http://twitter.com/matrixdotorg/status/12345",
                 shorthand=False,
+                await_result=False,
             )
-            request.render(self.preview_url)
             self.pump()
 
             client = self.reactor.tcpClients[0][2].buildProtocol(None)
diff --git a/tests/rest/test_health.py b/tests/rest/test_health.py
index 2d021f6565..02a46e5fda 100644
--- a/tests/rest/test_health.py
+++ b/tests/rest/test_health.py
@@ -20,15 +20,12 @@ from tests import unittest
 
 
 class HealthCheckTests(unittest.HomeserverTestCase):
-    def setUp(self):
-        super().setUp()
-
+    def create_test_resource(self):
         # replace the JsonResource with a HealthResource.
-        self.resource = HealthResource()
+        return HealthResource()
 
     def test_health(self):
         request, channel = self.make_request("GET", "/health", shorthand=False)
-        self.render(request)
 
         self.assertEqual(request.code, 200)
         self.assertEqual(channel.result["body"], b"OK")
diff --git a/tests/rest/test_well_known.py b/tests/rest/test_well_known.py
index dcd65c2a50..6a930f4148 100644
--- a/tests/rest/test_well_known.py
+++ b/tests/rest/test_well_known.py
@@ -20,11 +20,9 @@ from tests import unittest
 
 
 class WellKnownTests(unittest.HomeserverTestCase):
-    def setUp(self):
-        super().setUp()
-
+    def create_test_resource(self):
         # replace the JsonResource with a WellKnownResource
-        self.resource = WellKnownResource(self.hs)
+        return WellKnownResource(self.hs)
 
     def test_well_known(self):
         self.hs.config.public_baseurl = "https://tesths"
@@ -33,7 +31,6 @@ class WellKnownTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/.well-known/matrix/client", shorthand=False
         )
-        self.render(request)
 
         self.assertEqual(request.code, 200)
         self.assertEqual(
@@ -50,6 +47,5 @@ class WellKnownTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/.well-known/matrix/client", shorthand=False
         )
-        self.render(request)
 
         self.assertEqual(request.code, 404)
diff --git a/tests/server.py b/tests/server.py
index 3dd2cfc072..a51ad0c14e 100644
--- a/tests/server.py
+++ b/tests/server.py
@@ -2,7 +2,7 @@ import json
 import logging
 from collections import deque
 from io import SEEK_END, BytesIO
-from typing import Callable
+from typing import Callable, Iterable, Optional, Tuple, Union
 
 import attr
 from typing_extensions import Deque
@@ -19,8 +19,8 @@ from twisted.internet.interfaces import (
 )
 from twisted.python.failure import Failure
 from twisted.test.proto_helpers import AccumulatingProtocol, MemoryReactorClock
-from twisted.web.http import unquote
 from twisted.web.http_headers import Headers
+from twisted.web.resource import IResource
 from twisted.web.server import Site
 
 from synapse.http.site import SynapseRequest
@@ -117,6 +117,25 @@ class FakeChannel:
     def transport(self):
         return self
 
+    def await_result(self, timeout: int = 100) -> None:
+        """
+        Wait until the request is finished.
+        """
+        self._reactor.run()
+        x = 0
+
+        while not self.result.get("done"):
+            # If there's a producer, tell it to resume producing so we get content
+            if self._producer:
+                self._producer.resumeProducing()
+
+            x += 1
+
+            if x > timeout:
+                raise TimedOutException("Timed out waiting for request to finish.")
+
+            self._reactor.advance(0.1)
+
 
 class FakeSite:
     """
@@ -128,9 +147,21 @@ class FakeSite:
     site_tag = "test"
     access_logger = logging.getLogger("synapse.access.http.fake")
 
+    def __init__(self, resource: IResource):
+        """
+
+        Args:
+            resource: the resource to be used for rendering all requests
+        """
+        self._resource = resource
+
+    def getResourceFor(self, request):
+        return self._resource
+
 
 def make_request(
     reactor,
+    site: Site,
     method,
     path,
     content=b"",
@@ -139,12 +170,19 @@ def make_request(
     shorthand=True,
     federation_auth_origin=None,
     content_is_form=False,
+    await_result: bool = True,
+    custom_headers: Optional[
+        Iterable[Tuple[Union[bytes, str], Union[bytes, str]]]
+    ] = None,
 ):
     """
-    Make a web request using the given method and path, feed it the
-    content, and return the Request and the Channel underneath.
+    Make a web request using the given method, path and content, and render it
+
+    Returns the Request and the Channel underneath.
 
     Args:
+        site: The twisted Site to use to render the request
+
         method (bytes/unicode): The HTTP request method ("verb").
         path (bytes/unicode): The HTTP path, suitably URL encoded (e.g.
         escaped UTF-8 & spaces and such).
@@ -157,6 +195,12 @@ def make_request(
         content_is_form: Whether the content is URL encoded form data. Adds the
             'Content-Type': 'application/x-www-form-urlencoded' header.
 
+        custom_headers: (name, value) pairs to add as request headers
+
+        await_result: whether to wait for the request to complete rendering. If true,
+             will pump the reactor until the the renderer tells the channel the request
+             is finished.
+
     Returns:
         Tuple[synapse.http.site.SynapseRequest, channel]
     """
@@ -178,18 +222,17 @@ def make_request(
     if not path.startswith(b"/"):
         path = b"/" + path
 
+    if isinstance(content, dict):
+        content = json.dumps(content).encode("utf8")
     if isinstance(content, str):
         content = content.encode("utf8")
 
-    site = FakeSite()
     channel = FakeChannel(site, reactor)
 
     req = request(channel)
-    req.process = lambda: b""
     req.content = BytesIO(content)
     # Twisted expects to be at the end of the content when parsing the request.
     req.content.seek(SEEK_END)
-    req.postpath = list(map(unquote, path[1:].split(b"/")))
 
     if access_token:
         req.requestHeaders.addRawHeader(
@@ -211,35 +254,16 @@ def make_request(
             # Assume the body is JSON
             req.requestHeaders.addRawHeader(b"Content-Type", b"application/json")
 
-    req.requestReceived(method, path, b"1.1")
-
-    return req, channel
-
+    if custom_headers:
+        for k, v in custom_headers:
+            req.requestHeaders.addRawHeader(k, v)
 
-def wait_until_result(clock, request, timeout=100):
-    """
-    Wait until the request is finished.
-    """
-    clock.run()
-    x = 0
-
-    while not request.finished:
-
-        # If there's a producer, tell it to resume producing so we get content
-        if request._channel._producer:
-            request._channel._producer.resumeProducing()
-
-        x += 1
-
-        if x > timeout:
-            raise TimedOutException("Timed out waiting for request to finish.")
-
-        clock.advance(0.1)
+    req.requestReceived(method, path, b"1.1")
 
+    if await_result:
+        channel.await_result()
 
-def render(request, resource, clock):
-    request.render(resource)
-    wait_until_result(clock, request)
+    return req, channel
 
 
 @implementer(IReactorPluggableNameResolver)
diff --git a/tests/server_notices/test_consent.py b/tests/server_notices/test_consent.py
index 872039c8f1..e0a9cd93ac 100644
--- a/tests/server_notices/test_consent.py
+++ b/tests/server_notices/test_consent.py
@@ -73,7 +73,6 @@ class ConsentNoticesTests(unittest.HomeserverTestCase):
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/sync", access_token=self.access_token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # Get the Room ID to join
@@ -85,14 +84,12 @@ class ConsentNoticesTests(unittest.HomeserverTestCase):
             "/_matrix/client/r0/rooms/" + room_id + "/join",
             access_token=self.access_token,
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # Sync again, to get the message in the room
         request, channel = self.make_request(
             "GET", "/_matrix/client/r0/sync", access_token=self.access_token
         )
-        self.render(request)
         self.assertEqual(channel.code, 200)
 
         # Get the message
diff --git a/tests/server_notices/test_resource_limits_server_notices.py b/tests/server_notices/test_resource_limits_server_notices.py
index 6382b19dc3..9c8027a5b2 100644
--- a/tests/server_notices/test_resource_limits_server_notices.py
+++ b/tests/server_notices/test_resource_limits_server_notices.py
@@ -306,7 +306,6 @@ class TestResourceLimitsServerNoticesWithRealRooms(unittest.HomeserverTestCase):
         tok = self.login("user", "password")
 
         request, channel = self.make_request("GET", "/sync?timeout=0", access_token=tok)
-        self.render(request)
 
         invites = channel.json_body["rooms"]["invite"]
         self.assertEqual(len(invites), 0, invites)
@@ -320,7 +319,6 @@ class TestResourceLimitsServerNoticesWithRealRooms(unittest.HomeserverTestCase):
         # Sync again to retrieve the events in the room, so we can check whether this
         # room has a notice in it.
         request, channel = self.make_request("GET", "/sync?timeout=0", access_token=tok)
-        self.render(request)
 
         # Scan the events in the room to search for a message from the server notices
         # user.
@@ -358,7 +356,6 @@ class TestResourceLimitsServerNoticesWithRealRooms(unittest.HomeserverTestCase):
             request, channel = self.make_request(
                 "GET", "/sync?timeout=0", access_token=tok,
             )
-            self.render(request)
 
             # Also retrieves the list of invites for this user. We don't care about that
             # one except if we're processing the last user, which should have received an
diff --git a/tests/storage/test_cleanup_extrems.py b/tests/storage/test_cleanup_extrems.py
index 5a1e5c4e66..c13a57dad1 100644
--- a/tests/storage/test_cleanup_extrems.py
+++ b/tests/storage/test_cleanup_extrems.py
@@ -309,36 +309,6 @@ class CleanupExtremDummyEventsTestCase(HomeserverTestCase):
         )
         self.assertTrue(len(latest_event_ids) < 10, len(latest_event_ids))
 
-    @patch("synapse.handlers.message._DUMMY_EVENT_ROOM_EXCLUSION_EXPIRY", new=0)
-    def test_send_dummy_event_without_consent(self):
-        self._create_extremity_rich_graph()
-        self._enable_consent_checking()
-
-        # Pump the reactor repeatedly so that the background updates have a
-        # chance to run. Attempt to add dummy event with user that has not consented
-        # Check that dummy event send fails.
-        self.pump(10 * 60)
-        latest_event_ids = self.get_success(
-            self.store.get_latest_event_ids_in_room(self.room_id)
-        )
-        self.assertTrue(len(latest_event_ids) == self.EXTREMITIES_COUNT)
-
-        # Create new user, and add consent
-        user2 = self.register_user("user2", "password")
-        token2 = self.login("user2", "password")
-        self.get_success(
-            self.store.user_set_consent_version(user2, self.CONSENT_VERSION)
-        )
-        self.helper.join(self.room_id, user2, tok=token2)
-
-        # Background updates should now cause a dummy event to be added to the graph
-        self.pump(10 * 60)
-
-        latest_event_ids = self.get_success(
-            self.store.get_latest_event_ids_in_room(self.room_id)
-        )
-        self.assertTrue(len(latest_event_ids) < 10, len(latest_event_ids))
-
     @patch("synapse.handlers.message._DUMMY_EVENT_ROOM_EXCLUSION_EXPIRY", new=250)
     def test_expiry_logic(self):
         """Simple test to ensure that _expire_rooms_to_exclude_from_dummy_event_insertion()
diff --git a/tests/storage/test_client_ips.py b/tests/storage/test_client_ips.py
index e96ca1c8ca..6bdde1a2ba 100644
--- a/tests/storage/test_client_ips.py
+++ b/tests/storage/test_client_ips.py
@@ -21,6 +21,7 @@ from synapse.http.site import XForwardedForRequest
 from synapse.rest.client.v1 import login
 
 from tests import unittest
+from tests.server import make_request
 from tests.test_utils import make_awaitable
 from tests.unittest import override_config
 
@@ -408,18 +409,18 @@ class ClientIpAuthTestCase(unittest.HomeserverTestCase):
         # Advance to a known time
         self.reactor.advance(123456 - self.reactor.seconds())
 
-        request, channel = self.make_request(
+        headers1 = {b"User-Agent": b"Mozzila pizza"}
+        headers1.update(headers)
+
+        make_request(
+            self.reactor,
+            self.site,
             "GET",
             "/_matrix/client/r0/admin/users/" + self.user_id,
             access_token=access_token,
+            custom_headers=headers1.items(),
             **make_request_args,
         )
-        request.requestHeaders.addRawHeader(b"User-Agent", b"Mozzila pizza")
-
-        # Add the optional headers
-        for h, v in headers.items():
-            request.requestHeaders.addRawHeader(h, v)
-        self.render(request)
 
         # Advance so the save loop occurs
         self.reactor.advance(100)
diff --git a/tests/test_mau.py b/tests/test_mau.py
index 654a6fa42d..c5ec6396a7 100644
--- a/tests/test_mau.py
+++ b/tests/test_mau.py
@@ -202,7 +202,6 @@ class TestMauLimit(unittest.HomeserverTestCase):
         )
 
         request, channel = self.make_request("POST", "/register", request_data)
-        self.render(request)
 
         if channel.code != 200:
             raise HttpResponseException(
@@ -215,7 +214,6 @@ class TestMauLimit(unittest.HomeserverTestCase):
 
     def do_sync_for_user(self, token):
         request, channel = self.make_request("GET", "/sync", access_token=token)
-        self.render(request)
 
         if channel.code != 200:
             raise HttpResponseException(
diff --git a/tests/test_server.py b/tests/test_server.py
index 655c918a15..c387a85f2e 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -26,9 +26,9 @@ from synapse.util import Clock
 
 from tests import unittest
 from tests.server import (
+    FakeSite,
     ThreadedMemoryReactorClock,
     make_request,
-    render,
     setup_test_homeserver,
 )
 
@@ -62,9 +62,8 @@ class JsonResourceTests(unittest.TestCase):
         )
 
         request, channel = make_request(
-            self.reactor, b"GET", b"/_matrix/foo/%E2%98%83?a=%E2%98%83"
+            self.reactor, FakeSite(res), b"GET", b"/_matrix/foo/%E2%98%83?a=%E2%98%83"
         )
-        render(request, res, self.reactor)
 
         self.assertEqual(request.args, {b"a": ["\N{SNOWMAN}".encode("utf8")]})
         self.assertEqual(got_kwargs, {"room_id": "\N{SNOWMAN}"})
@@ -83,8 +82,7 @@ class JsonResourceTests(unittest.TestCase):
             "GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
         )
 
-        request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
-        render(request, res, self.reactor)
+        _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/_matrix/foo")
 
         self.assertEqual(channel.result["code"], b"500")
 
@@ -108,8 +106,7 @@ class JsonResourceTests(unittest.TestCase):
             "GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
         )
 
-        request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
-        render(request, res, self.reactor)
+        _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/_matrix/foo")
 
         self.assertEqual(channel.result["code"], b"500")
 
@@ -127,8 +124,7 @@ class JsonResourceTests(unittest.TestCase):
             "GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
         )
 
-        request, channel = make_request(self.reactor, b"GET", b"/_matrix/foo")
-        render(request, res, self.reactor)
+        _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/_matrix/foo")
 
         self.assertEqual(channel.result["code"], b"403")
         self.assertEqual(channel.json_body["error"], "Forbidden!!one!")
@@ -150,8 +146,9 @@ class JsonResourceTests(unittest.TestCase):
             "GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
         )
 
-        request, channel = make_request(self.reactor, b"GET", b"/_matrix/foobar")
-        render(request, res, self.reactor)
+        _, channel = make_request(
+            self.reactor, FakeSite(res), b"GET", b"/_matrix/foobar"
+        )
 
         self.assertEqual(channel.result["code"], b"400")
         self.assertEqual(channel.json_body["error"], "Unrecognized request")
@@ -173,8 +170,7 @@ class JsonResourceTests(unittest.TestCase):
         )
 
         # The path was registered as GET, but this is a HEAD request.
-        request, channel = make_request(self.reactor, b"HEAD", b"/_matrix/foo")
-        render(request, res, self.reactor)
+        _, channel = make_request(self.reactor, FakeSite(res), b"HEAD", b"/_matrix/foo")
 
         self.assertEqual(channel.result["code"], b"200")
         self.assertNotIn("body", channel.result)
@@ -196,9 +192,6 @@ class OptionsResourceTests(unittest.TestCase):
 
     def _make_request(self, method, path):
         """Create a request from the method/path and return a channel with the response."""
-        request, channel = make_request(self.reactor, method, path, shorthand=False)
-        request.prepath = []  # This doesn't get set properly by make_request.
-
         # Create a site and query for the resource.
         site = SynapseSite(
             "test",
@@ -207,11 +200,9 @@ class OptionsResourceTests(unittest.TestCase):
             self.resource,
             "1.0",
         )
-        request.site = site
-        resource = site.getResourceFor(request)
 
-        # Finally, render the resource and return the channel.
-        render(request, resource, self.reactor)
+        # render the request and return the channel
+        _, channel = make_request(self.reactor, site, method, path, shorthand=False)
         return channel
 
     def test_unknown_options_request(self):
@@ -284,8 +275,7 @@ class WrapHtmlRequestHandlerTests(unittest.TestCase):
         res = WrapHtmlRequestHandlerTests.TestResource()
         res.callback = callback
 
-        request, channel = make_request(self.reactor, b"GET", b"/path")
-        render(request, res, self.reactor)
+        _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/path")
 
         self.assertEqual(channel.result["code"], b"200")
         body = channel.result["body"]
@@ -303,8 +293,7 @@ class WrapHtmlRequestHandlerTests(unittest.TestCase):
         res = WrapHtmlRequestHandlerTests.TestResource()
         res.callback = callback
 
-        request, channel = make_request(self.reactor, b"GET", b"/path")
-        render(request, res, self.reactor)
+        _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/path")
 
         self.assertEqual(channel.result["code"], b"301")
         headers = channel.result["headers"]
@@ -325,8 +314,7 @@ class WrapHtmlRequestHandlerTests(unittest.TestCase):
         res = WrapHtmlRequestHandlerTests.TestResource()
         res.callback = callback
 
-        request, channel = make_request(self.reactor, b"GET", b"/path")
-        render(request, res, self.reactor)
+        _, channel = make_request(self.reactor, FakeSite(res), b"GET", b"/path")
 
         self.assertEqual(channel.result["code"], b"304")
         headers = channel.result["headers"]
@@ -345,8 +333,7 @@ class WrapHtmlRequestHandlerTests(unittest.TestCase):
         res = WrapHtmlRequestHandlerTests.TestResource()
         res.callback = callback
 
-        request, channel = make_request(self.reactor, b"HEAD", b"/path")
-        render(request, res, self.reactor)
+        _, channel = make_request(self.reactor, FakeSite(res), b"HEAD", b"/path")
 
         self.assertEqual(channel.result["code"], b"200")
         self.assertNotIn("body", channel.result)
diff --git a/tests/test_state.py b/tests/test_state.py
index 80b0ccbc40..6227a3ba95 100644
--- a/tests/test_state.py
+++ b/tests/test_state.py
@@ -169,6 +169,7 @@ class StateTestCase(unittest.TestCase):
                 "get_state_handler",
                 "get_clock",
                 "get_state_resolution_handler",
+                "hostname",
             ]
         )
         hs.config = default_config("tesths", True)
diff --git a/tests/test_terms_auth.py b/tests/test_terms_auth.py
index b89798336c..71580b454d 100644
--- a/tests/test_terms_auth.py
+++ b/tests/test_terms_auth.py
@@ -54,7 +54,6 @@ class TermsTestCase(unittest.HomeserverTestCase):
         # Do a UI auth request
         request_data = json.dumps({"username": "kermit", "password": "monkey"})
         request, channel = self.make_request(b"POST", self.url, request_data)
-        self.render(request)
 
         self.assertEquals(channel.result["code"], b"401", channel.result)
 
@@ -98,7 +97,6 @@ class TermsTestCase(unittest.HomeserverTestCase):
         self.registration_handler.check_username = Mock(return_value=True)
 
         request, channel = self.make_request(b"POST", self.url, request_data)
-        self.render(request)
 
         # We don't bother checking that the response is correct - we'll leave that to
         # other tests. We just want to make sure we're on the right path.
@@ -116,7 +114,6 @@ class TermsTestCase(unittest.HomeserverTestCase):
             }
         )
         request, channel = self.make_request(b"POST", self.url, request_data)
-        self.render(request)
 
         # We're interested in getting a response that looks like a successful
         # registration, not so much that the details are exactly what we want.
diff --git a/tests/unittest.py b/tests/unittest.py
index e36ac89196..c7c889c405 100644
--- a/tests/unittest.py
+++ b/tests/unittest.py
@@ -30,6 +30,7 @@ from twisted.internet.defer import Deferred, ensureDeferred, succeed
 from twisted.python.failure import Failure
 from twisted.python.threadpool import ThreadPool
 from twisted.trial import unittest
+from twisted.web.resource import Resource
 
 from synapse.api.constants import EventTypes, Membership
 from synapse.config.homeserver import HomeServerConfig
@@ -47,13 +48,7 @@ from synapse.server import HomeServer
 from synapse.types import UserID, create_requester
 from synapse.util.ratelimitutils import FederationRateLimiter
 
-from tests.server import (
-    FakeChannel,
-    get_clock,
-    make_request,
-    render,
-    setup_test_homeserver,
-)
+from tests.server import FakeChannel, get_clock, make_request, setup_test_homeserver
 from tests.test_utils import event_injection, setup_awaitable_errors
 from tests.test_utils.logging_setup import setup_logging
 from tests.utils import default_config, setupdb
@@ -239,10 +234,8 @@ class HomeserverTestCase(TestCase):
         if not isinstance(self.hs, HomeServer):
             raise Exception("A homeserver wasn't returned, but %r" % (self.hs,))
 
-        # Register the resources
-        self.resource = self.create_test_json_resource()
-
-        # create a site to wrap the resource.
+        # create the root resource, and a site to wrap it.
+        self.resource = self.create_test_resource()
         self.site = SynapseSite(
             logger_name="synapse.access.http.fake",
             site_tag=self.hs.config.server.server_name,
@@ -253,7 +246,7 @@ class HomeserverTestCase(TestCase):
 
         from tests.rest.client.v1.utils import RestHelper
 
-        self.helper = RestHelper(self.hs, self.resource, getattr(self, "user_id", None))
+        self.helper = RestHelper(self.hs, self.site, getattr(self, "user_id", None))
 
         if hasattr(self, "user_id"):
             if self.hijack_auth:
@@ -323,15 +316,12 @@ class HomeserverTestCase(TestCase):
         hs = self.setup_test_homeserver()
         return hs
 
-    def create_test_json_resource(self):
+    def create_test_resource(self) -> Resource:
         """
-        Create a test JsonResource, with the relevant servlets registerd to it
-
-        The default implementation calls each function in `servlets` to do the
-        registration.
+        Create a the root resource for the test server.
 
-        Returns:
-            JsonResource:
+        The default implementation creates a JsonResource and calls each function in
+        `servlets` to register servletes against it
         """
         resource = JsonResource(self.hs)
 
@@ -381,6 +371,7 @@ class HomeserverTestCase(TestCase):
         shorthand: bool = True,
         federation_auth_origin: str = None,
         content_is_form: bool = False,
+        await_result: bool = True,
     ) -> Tuple[SynapseRequest, FakeChannel]:
         ...
 
@@ -395,6 +386,7 @@ class HomeserverTestCase(TestCase):
         shorthand: bool = True,
         federation_auth_origin: str = None,
         content_is_form: bool = False,
+        await_result: bool = True,
     ) -> Tuple[T, FakeChannel]:
         ...
 
@@ -408,6 +400,7 @@ class HomeserverTestCase(TestCase):
         shorthand: bool = True,
         federation_auth_origin: str = None,
         content_is_form: bool = False,
+        await_result: bool = True,
     ) -> Tuple[T, FakeChannel]:
         """
         Create a SynapseRequest at the path using the method and containing the
@@ -426,14 +419,16 @@ class HomeserverTestCase(TestCase):
             content_is_form: Whether the content is URL encoded form data. Adds the
                 'Content-Type': 'application/x-www-form-urlencoded' header.
 
+            await_result: whether to wait for the request to complete rendering. If
+                 true (the default), will pump the test reactor until the the renderer
+                 tells the channel the request is finished.
+
         Returns:
             Tuple[synapse.http.site.SynapseRequest, channel]
         """
-        if isinstance(content, dict):
-            content = json.dumps(content).encode("utf8")
-
         return make_request(
             self.reactor,
+            self.site,
             method,
             path,
             content,
@@ -442,18 +437,9 @@ class HomeserverTestCase(TestCase):
             shorthand,
             federation_auth_origin,
             content_is_form,
+            await_result,
         )
 
-    def render(self, request):
-        """
-        Render a request against the resources registered by the test class's
-        servlets.
-
-        Args:
-            request (synapse.http.site.SynapseRequest): The request to render.
-        """
-        render(request, self.resource, self.reactor)
-
     def setup_test_homeserver(self, *args, **kwargs):
         """
         Set up the test homeserver, meant to be called by the overridable
@@ -569,7 +555,6 @@ class HomeserverTestCase(TestCase):
 
         # Create the user
         request, channel = self.make_request("GET", "/_matrix/client/r0/admin/register")
-        self.render(request)
         self.assertEqual(channel.code, 200, msg=channel.result)
         nonce = channel.json_body["nonce"]
 
@@ -597,7 +582,6 @@ class HomeserverTestCase(TestCase):
         request, channel = self.make_request(
             "POST", "/_matrix/client/r0/admin/register", body.encode("utf8")
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.json_body)
 
         user_id = channel.json_body["user_id"]
@@ -616,7 +600,6 @@ class HomeserverTestCase(TestCase):
         request, channel = self.make_request(
             "POST", "/_matrix/client/r0/login", json.dumps(body).encode("utf8")
         )
-        self.render(request)
         self.assertEqual(channel.code, 200, channel.result)
 
         access_token = channel.json_body["access_token"]
@@ -685,7 +668,6 @@ class HomeserverTestCase(TestCase):
         request, channel = self.make_request(
             "POST", "/_matrix/client/r0/login", json.dumps(body).encode("utf8")
         )
-        self.render(request)
         self.assertEqual(channel.code, 403, channel.result)
 
     def inject_room_member(self, room: str, user: str, membership: Membership) -> None: