Repository: python-telegram-bot/python-telegram-bot
Pull request: Fix method signatures and expand `test_official`
Closes #2367 

Notes:
- Didn't remove the args/attributes of `ChatMember` yet as that's part of #2346. But it's been effectively removed. (Edit: now removed.)
- Removed `__hash__()` of `VoiceChatParticipantsInvited` since its attribute is actually optional. (Edit: added it back)
- Can someone confirm if I did the `de_json` of `ChatMember*` correctly?

Tests:
- rework test_chatmember to easily test all attributes in one pass
- `test_official` now scans if argument if required or not.



Problem statement:
[BUG] Wrong signature of API objects/methods
in `Bot.edit_message_mediag`, for some reason `media` is a optional kwarg, which is clearly wrong. Changing that is breaking, so putting on the v14 milestone.

We could think about expanding `test_official` to parse the offical docs in order to check such things.

Hints from issue discussion:
We have a similar issue with the `ChatMember*` classes: For the subclasses currently all arguments are optional (except `user` and `status`), while most of them actually aren't. B/C users should never have to manually instantiate a `ChatMember*` manually, that's currently only a documentation/type hinting problem, but it should be fixed at some point.

We should really improve `test_official` for that …
I like adding that to the test.
Okay its not just these 2, there's many methods whose signatures are wrong...

Commit message: fix pre-commit

didn't commit that weird

Patch to review:
diff --git a/telegram/bot.py b/telegram/bot.py
index de445d8b467..3a316b3b3a4 100644
--- a/telegram/bot.py
+++ b/telegram/bot.py
@@ -1753,7 +1753,7 @@ def send_venue(
               :obj:`title` and :obj:`address` and optionally :obj:`foursquare_id` and
               :obj:`foursquare_type` or optionally :obj:`google_place_id` and
               :obj:`google_place_type`.
-            * Foursquare details and Google Pace details are mutually exclusive. However, this
+            * Foursquare details and Google Place details are mutually exclusive. However, this
               behaviour is undocumented and might be changed by Telegram.
 
         Args:
@@ -2657,10 +2657,10 @@ def edit_message_caption(
     @log
     def edit_message_media(
         self,
+        media: 'InputMedia',
         chat_id: Union[str, int] = None,
         message_id: int = None,
         inline_message_id: int = None,
-        media: 'InputMedia' = None,
         reply_markup: InlineKeyboardMarkup = None,
         timeout: ODVInput[float] = DEFAULT_NONE,
         api_kwargs: JSONDict = None,
@@ -2673,6 +2673,8 @@ def edit_message_media(
         ``file_id`` or specify a URL.
 
         Args:
+            media (:class:`telegram.InputMedia`): An object for a new media content
+                of the message.
             chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not
                 specified. Unique identifier for the target chat or username of the target channel
                 (in the format ``@channelusername``).
@@ -2680,8 +2682,6 @@ def edit_message_media(
                 Identifier of the message to edit.
             inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not
                 specified. Identifier of the inline message.
-            media (:class:`telegram.InputMedia`): An object for a new media content
-                of the message.
             reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized
                 object for an inline keyboard.
             timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
@@ -2691,7 +2691,7 @@ def edit_message_media(
                 Telegram API.
 
         Returns:
-            :class:`telegram.Message`: On success, if edited message is sent by the bot, the
+            :class:`telegram.Message`: On success, if edited message is not an inline message, the
             edited Message is returned, otherwise :obj:`True` is returned.
 
         Raises:
@@ -2868,7 +2868,7 @@ def get_updates(
     @log
     def set_webhook(
         self,
-        url: str = None,
+        url: str,
         certificate: FileInput = None,
         timeout: ODVInput[float] = DEFAULT_NONE,
         max_connections: int = 40,
@@ -2939,10 +2939,8 @@ def set_webhook(
         .. _`guide to Webhooks`: https://core.telegram.org/bots/webhooks
 
         """
-        data: JSONDict = {}
+        data: JSONDict = {'url': url}
 
-        if url is not None:
-            data['url'] = url
         if certificate:
             data['certificate'] = parse_file_input(certificate)
         if max_connections is not None:
@@ -4231,7 +4229,7 @@ def set_chat_title(
     def set_chat_description(
         self,
         chat_id: Union[str, int],
-        description: str,
+        description: str = None,
         timeout: ODVInput[float] = DEFAULT_NONE,
         api_kwargs: JSONDict = None,
     ) -> bool:
@@ -4243,7 +4241,7 @@ def set_chat_description(
         Args:
             chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
                 of the target channel (in the format ``@channelusername``).
-            description (:obj:`str`): New chat description, 0-255 characters.
+            description (:obj:`str`, optional): New chat description, 0-255 characters.
             timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
                 the read timeout from the server (instead of the one specified during creation of
                 the connection pool).
@@ -4257,7 +4255,10 @@ def set_chat_description(
             :class:`telegram.error.TelegramError`
 
         """
-        data: JSONDict = {'chat_id': chat_id, 'description': description}
+        data: JSONDict = {'chat_id': chat_id}
+
+        if description is not None:
+            data['description'] = description
 
         result = self._post('setChatDescription', data, timeout=timeout, api_kwargs=api_kwargs)
 
diff --git a/telegram/callbackquery.py b/telegram/callbackquery.py
index 9630bd46fed..011d50b555d 100644
--- a/telegram/callbackquery.py
+++ b/telegram/callbackquery.py
@@ -319,7 +319,7 @@ def edit_message_reply_markup(
 
     def edit_message_media(
         self,
-        media: 'InputMedia' = None,
+        media: 'InputMedia',
         reply_markup: 'InlineKeyboardMarkup' = None,
         timeout: ODVInput[float] = DEFAULT_NONE,
         api_kwargs: JSONDict = None,
@@ -337,7 +337,7 @@ def edit_message_media(
         :meth:`telegram.Bot.edit_message_media` and :meth:`telegram.Message.edit_media`.
 
         Returns:
-            :class:`telegram.Message`: On success, if edited message is sent by the bot, the
+            :class:`telegram.Message`: On success, if edited message is not an inline message, the
             edited Message is returned, otherwise :obj:`True` is returned.
 
         """
diff --git a/telegram/chatmember.py b/telegram/chatmember.py
index 445ba35a97b..e7aefad8ac4 100644
--- a/telegram/chatmember.py
+++ b/telegram/chatmember.py
@@ -398,18 +398,18 @@ class ChatMemberOwner(ChatMember):
 
     Args:
         user (:class:`telegram.User`): Information about the user.
-        custom_title (:obj:`str`, optional): Custom title for this user.
-        is_anonymous (:obj:`bool`, optional): :obj:`True`, if the
+        is_anonymous (:obj:`bool`): :obj:`True`, if the
             user's presence in the chat is hidden.
+        custom_title (:obj:`str`, optional): Custom title for this user.
 
     Attributes:
         status (:obj:`str`): The member's status in the chat,
             always :attr:`telegram.ChatMember.CREATOR`.
         user (:class:`telegram.User`): Information about the user.
+        is_anonymous (:obj:`bool`): :obj:`True`, if the user's
+            presence in the chat is hidden.
         custom_title (:obj:`str`): Optional. Custom title for
             this user.
-        is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's
-            presence in the chat is hidden.
     """
 
     __slots__ = ()
@@ -417,9 +417,9 @@ class ChatMemberOwner(ChatMember):
     def __init__(
         self,
         user: User,
+        is_anonymous: bool,
         custom_title: str = None,
-        is_anonymous: bool = None,
-        **_kwargs: Any,
+        **_kwargs: object,
     ):
         super().__init__(
             status=ChatMember.CREATOR,
@@ -437,71 +437,71 @@ class ChatMemberAdministrator(ChatMember):
 
     Args:
         user (:class:`telegram.User`): Information about the user.
-        can_be_edited (:obj:`bool`, optional): :obj:`True`, if the bot
+        can_be_edited (:obj:`bool`): :obj:`True`, if the bot
             is allowed to edit administrator privileges of that user.
-        custom_title (:obj:`str`, optional): Custom title for this user.
-        is_anonymous (:obj:`bool`, optional): :obj:`True`, if the  user's
+        is_anonymous (:obj:`bool`): :obj:`True`, if the  user's
             presence in the chat is hidden.
-        can_manage_chat (:obj:`bool`, optional): :obj:`True`, if the administrator
+        can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator
             can access the chat event log, chat statistics, message statistics in
             channels, see channel members, see anonymous administrators in supergroups
             and ignore slow mode. Implied by any other administrator privilege.
-        can_post_messages (:obj:`bool`, optional): :obj:`True`, if the
-            administrator can post in the channel, channels only.
-        can_edit_messages (:obj:`bool`, optional): :obj:`True`, if the
-            administrator can edit messages of other users and can pin
-            messages; channels only.
-        can_delete_messages (:obj:`bool`, optional): :obj:`True`, if the
+        can_delete_messages (:obj:`bool`): :obj:`True`, if the
             administrator can delete messages of other users.
-        can_manage_voice_chats (:obj:`bool`, optional): :obj:`True`, if the
+        can_manage_voice_chats (:obj:`bool`): :obj:`True`, if the
             administrator can manage voice chats.
-        can_restrict_members (:obj:`bool`, optional): :obj:`True`, if the
+        can_restrict_members (:obj:`bool`): :obj:`True`, if the
             administrator can restrict, ban or unban chat members.
-        can_promote_members (:obj:`bool`, optional): :obj:`True`, if the administrator
+        can_promote_members (:obj:`bool`): :obj:`True`, if the administrator
             can add new administrators with a subset of his own privileges or demote
             administrators that he has promoted, directly or indirectly (promoted by
             administrators that were appointed by the user).
-        can_change_info (:obj:`bool`, optional): :obj:`True`, if the user can change
+        can_change_info (:obj:`bool`): :obj:`True`, if the user can change
             the chat title, photo and other settings.
-        can_invite_users (:obj:`bool`, optional): :obj:`True`, if the user can invite
+        can_invite_users (:obj:`bool`): :obj:`True`, if the user can invite
             new users to the chat.
+        can_post_messages (:obj:`bool`, optional): :obj:`True`, if the
+            administrator can post in the channel, channels only.
+        can_edit_messages (:obj:`bool`, optional): :obj:`True`, if the
+            administrator can edit messages of other users and can pin
+            messages; channels only.
         can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
             to pin messages; groups and supergroups only.
+        custom_title (:obj:`str`, optional): Custom title for this user.
 
     Attributes:
         status (:obj:`str`): The member's status in the chat,
             always :attr:`telegram.ChatMember.ADMINISTRATOR`.
         user (:class:`telegram.User`): Information about the user.
-        can_be_edited (:obj:`bool`): Optional. :obj:`True`, if the bot
+        can_be_edited (:obj:`bool`): :obj:`True`, if the bot
             is allowed to edit administrator privileges of that user.
-        custom_title (:obj:`str`): Optional. Custom title for this user.
-        is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the  user's
+        is_anonymous (:obj:`bool`): :obj:`True`, if the  user's
             presence in the chat is hidden.
-        can_manage_chat (:obj:`bool`): Optional. :obj:`True`, if the administrator
+        can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator
             can access the chat event log, chat statistics, message statistics in
             channels, see channel members, see anonymous administrators in supergroups
             and ignore slow mode. Implied by any other administrator privilege.
-        can_post_messages (:obj:`bool`): Optional. :obj:`True`, if the
-            administrator can post in the channel, channels only.
-        can_edit_messages (:obj:`bool`): Optional. :obj:`True`, if the
-            administrator can edit messages of other users and can pin
-            messages; channels only.
-        can_delete_messages (:obj:`bool`): Optional. :obj:`True`, if the
+        can_delete_messages (:obj:`bool`): :obj:`True`, if the
             administrator can delete messages of other users.
-        can_manage_voice_chats (:obj:`bool`): Optional. :obj:`True`, if the
+        can_manage_voice_chats (:obj:`bool`): :obj:`True`, if the
             administrator can manage voice chats.
-        can_restrict_members (:obj:`bool`): Optional. :obj:`True`, if the
+        can_restrict_members (:obj:`bool`): :obj:`True`, if the
             administrator can restrict, ban or unban chat members.
-        can_promote_members (:obj:`bool`): Optional. :obj:`True`, if the administrator
+        can_promote_members (:obj:`bool`): :obj:`True`, if the administrator
             can add new administrators with a subset of his own privileges or demote
             administrators that he has promoted, directly or indirectly (promoted by
             administrators that were appointed by the user).
-        can_change_info (:obj:`bool`): Optional. :obj:`True`, if the user can change
+        can_change_info (:obj:`bool`): :obj:`True`, if the user can change
             the chat title, photo and other settings.
-        can_invite_users (:obj:`bool`): Optional. :obj:`True`, if the user can invite
+        can_invite_users (:obj:`bool`): :obj:`True`, if the user can invite
             new users to the chat.
+        can_post_messages (:obj:`bool`): Optional. :obj:`True`, if the
+            administrator can post in the channel, channels only.
+        can_edit_messages (:obj:`bool`): Optional. :obj:`True`, if the
+            administrator can edit messages of other users and can pin
+            messages; channels only.
         can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
             to pin messages; groups and supergroups only.
+        custom_title (:obj:`str`): Optional. Custom title for this user.
     """
 
     __slots__ = ()
@@ -509,20 +509,20 @@ class ChatMemberAdministrator(ChatMember):
     def __init__(
         self,
         user: User,
-        can_be_edited: bool = None,
-        custom_title: str = None,
-        is_anonymous: bool = None,
-        can_manage_chat: bool = None,
+        can_be_edited: bool,
+        is_anonymous: bool,
+        can_manage_chat: bool,
+        can_delete_messages: bool,
+        can_manage_voice_chats: bool,
+        can_restrict_members: bool,
+        can_promote_members: bool,
+        can_change_info: bool,
+        can_invite_users: bool,
         can_post_messages: bool = None,
         can_edit_messages: bool = None,
-        can_delete_messages: bool = None,
-        can_manage_voice_chats: bool = None,
-        can_restrict_members: bool = None,
-        can_promote_members: bool = None,
-        can_change_info: bool = None,
-        can_invite_users: bool = None,
         can_pin_messages: bool = None,
-        **_kwargs: Any,
+        custom_title: str = None,
+        **_kwargs: object,
     ):
         super().__init__(
             status=ChatMember.ADMINISTRATOR,
@@ -562,7 +562,7 @@ class ChatMemberMember(ChatMember):
 
     __slots__ = ()
 
-    def __init__(self, user: User, **_kwargs: Any):
+    def __init__(self, user: User, **_kwargs: object):
         super().__init__(status=ChatMember.MEMBER, user=user)
 
 
@@ -575,50 +575,50 @@ class ChatMemberRestricted(ChatMember):
 
     Args:
         user (:class:`telegram.User`): Information about the user.
-        is_member (:obj:`bool`, optional): :obj:`True`, if the user is a
+        is_member (:obj:`bool`): :obj:`True`, if the user is a
             member of the chat at the moment of the request.
-        can_change_info (:obj:`bool`, optional): :obj:`True`, if the user can change
+        can_change_info (:obj:`bool`): :obj:`True`, if the user can change
             the chat title, photo and other settings.
-        can_invite_users (:obj:`bool`, optional): :obj:`True`, if the user can invite
+        can_invite_users (:obj:`bool`): :obj:`True`, if the user can invite
             new users to the chat.
-        can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+        can_pin_messages (:obj:`bool`): :obj:`True`, if the user is allowed
             to pin messages; groups and supergroups only.
-        can_send_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+        can_send_messages (:obj:`bool`): :obj:`True`, if the user is allowed
             to send text messages, contacts, locations and venues.
-        can_send_media_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+        can_send_media_messages (:obj:`bool`): :obj:`True`, if the user is allowed
             to send audios, documents, photos, videos, video notes and voice notes.
-        can_send_polls (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+        can_send_polls (:obj:`bool`): :obj:`True`, if the user is allowed
             to send polls.
-        can_send_other_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
+        can_send_other_messages (:obj:`bool`): :obj:`True`, if the user is allowed
             to send animations, games, stickers and use inline bots.
-        can_add_web_page_previews (:obj:`bool`, optional): :obj:`True`, if the user is
+        can_add_web_page_previews (:obj:`bool`): :obj:`True`, if the user is
            allowed to add web page previews to their messages.
-        until_date (:class:`datetime.datetime`, optional): Date when restrictions
+        until_date (:class:`datetime.datetime`): Date when restrictions
            will be lifted for this user.
 
     Attributes:
         status (:obj:`str`): The member's status in the chat,
             always :attr:`telegram.ChatMember.RESTRICTED`.
         user (:class:`telegram.User`): Information about the user.
-        is_member (:obj:`bool`): Optional. :obj:`True`, if the user is a
+        is_member (:obj:`bool`): :obj:`True`, if the user is a
             member of the chat at the moment of the request.
-        can_change_info (:obj:`bool`): Optional. :obj:`True`, if the user can change
+        can_change_info (:obj:`bool`): :obj:`True`, if the user can change
             the chat title, photo and other settings.
-        can_invite_users (:obj:`bool`): Optional. :obj:`True`, if the user can invite
+        can_invite_users (:obj:`bool`): :obj:`True`, if the user can invite
             new users to the chat.
-        can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
+        can_pin_messages (:obj:`bool`): :obj:`True`, if the user is allowed
             to pin messages; groups and supergroups only.
-        can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
+        can_send_messages (:obj:`bool`): :obj:`True`, if the user is allowed
             to send text messages, contacts, locations and venues.
-        can_send_media_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
+        can_send_media_messages (:obj:`bool`): :obj:`True`, if the user is allowed
             to send audios, documents, photos, videos, video notes and voice notes.
-        can_send_polls (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
+        can_send_polls (:obj:`bool`): :obj:`True`, if the user is allowed
             to send polls.
-        can_send_other_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
+        can_send_other_messages (:obj:`bool`): :obj:`True`, if the user is allowed
             to send animations, games, stickers and use inline bots.
-        can_add_web_page_previews (:obj:`bool`): Optional. :obj:`True`, if the user is
+        can_add_web_page_previews (:obj:`bool`): :obj:`True`, if the user is
            allowed to add web page previews to their messages.
-        until_date (:class:`datetime.datetime`): Optional. Date when restrictions
+        until_date (:class:`datetime.datetime`): Date when restrictions
            will be lifted for this user.
 
     """
@@ -628,17 +628,17 @@ class ChatMemberRestricted(ChatMember):
     def __init__(
         self,
         user: User,
-        is_member: bool = None,
-        can_change_info: bool = None,
-        can_invite_users: bool = None,
-        can_pin_messages: bool = None,
-        can_send_messages: bool = None,
-        can_send_media_messages: bool = None,
-        can_send_polls: bool = None,
-        can_send_other_messages: bool = None,
-        can_add_web_page_previews: bool = None,
-        until_date: datetime.datetime = None,
-        **_kwargs: Any,
+        is_member: bool,
+        can_change_info: bool,
+        can_invite_users: bool,
+        can_pin_messages: bool,
+        can_send_messages: bool,
+        can_send_media_messages: bool,
+        can_send_polls: bool,
+        can_send_other_messages: bool,
+        can_add_web_page_previews: bool,
+        until_date: datetime.datetime,
+        **_kwargs: object,
     ):
         super().__init__(
             status=ChatMember.RESTRICTED,
@@ -674,7 +674,7 @@ class ChatMemberLeft(ChatMember):
 
     __slots__ = ()
 
-    def __init__(self, user: User, **_kwargs: Any):
+    def __init__(self, user: User, **_kwargs: object):
         super().__init__(status=ChatMember.LEFT, user=user)
 
 
@@ -687,14 +687,14 @@ class ChatMemberBanned(ChatMember):
 
     Args:
         user (:class:`telegram.User`): Information about the user.
-        until_date (:class:`datetime.datetime`, optional): Date when restrictions
+        until_date (:class:`datetime.datetime`): Date when restrictions
            will be lifted for this user.
 
     Attributes:
         status (:obj:`str`): The member's status in the chat,
             always :attr:`telegram.ChatMember.KICKED`.
         user (:class:`telegram.User`): Information about the user.
-        until_date (:class:`datetime.datetime`): Optional. Date when restrictions
+        until_date (:class:`datetime.datetime`): Date when restrictions
            will be lifted for this user.
 
     """
@@ -704,8 +704,8 @@ class ChatMemberBanned(ChatMember):
     def __init__(
         self,
         user: User,
-        until_date: datetime.datetime = None,
-        **_kwargs: Any,
+        until_date: datetime.datetime,
+        **_kwargs: object,
     ):
         super().__init__(
             status=ChatMember.KICKED,
diff --git a/telegram/message.py b/telegram/message.py
index bd80785bae2..3d68f67ad2b 100644
--- a/telegram/message.py
+++ b/telegram/message.py
@@ -1987,7 +1987,7 @@ def edit_caption(
 
     def edit_media(
         self,
-        media: 'InputMedia' = None,
+        media: 'InputMedia',
         reply_markup: InlineKeyboardMarkup = None,
         timeout: ODVInput[float] = DEFAULT_NONE,
         api_kwargs: JSONDict = None,
@@ -2008,14 +2008,14 @@ def edit_media(
             behaviour is undocumented and might be changed by Telegram.
 
         Returns:
-            :class:`telegram.Message`: On success, if edited message is sent by the bot, the
+            :class:`telegram.Message`: On success, if edited message is not an inline message, the
             edited Message is returned, otherwise ``True`` is returned.
 
         """
         return self.bot.edit_message_media(
+            media=media,
             chat_id=self.chat_id,
             message_id=self.message_id,
-            media=media,
             reply_markup=reply_markup,
             timeout=timeout,
             api_kwargs=api_kwargs,
diff --git a/telegram/passport/encryptedpassportelement.py b/telegram/passport/encryptedpassportelement.py
index 700655e8cfc..afa22a190c6 100644
--- a/telegram/passport/encryptedpassportelement.py
+++ b/telegram/passport/encryptedpassportelement.py
@@ -52,6 +52,8 @@ class EncryptedPassportElement(TelegramObject):
             "identity_card", "internal_passport", "address", "utility_bill", "bank_statement",
             "rental_agreement", "passport_registration", "temporary_registration", "phone_number",
             "email".
+        hash (:obj:`str`): Base64-encoded element hash for using in
+            :class:`telegram.PassportElementErrorUnspecified`.
         data (:class:`telegram.PersonalDetails` | :class:`telegram.IdDocument` | \
             :class:`telegram.ResidentialAddress` | :obj:`str`, optional):
             Decrypted or encrypted data, available for "personal_details", "passport",
@@ -77,8 +79,6 @@ class EncryptedPassportElement(TelegramObject):
             requested for "passport", "driver_license", "identity_card", "internal_passport",
             "utility_bill", "bank_statement", "rental_agreement", "passport_registration" and
             "temporary_registration" types.
-        hash (:obj:`str`): Base64-encoded element hash for using in
-            :class:`telegram.PassportElementErrorUnspecified`.
         bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
         **kwargs (:obj:`dict`): Arbitrary keyword arguments.
 
@@ -87,6 +87,8 @@ class EncryptedPassportElement(TelegramObject):
             "identity_card", "internal_passport", "address", "utility_bill", "bank_statement",
             "rental_agreement", "passport_registration", "temporary_registration", "phone_number",
             "email".
+        hash (:obj:`str`): Base64-encoded element hash for using in
+            :class:`telegram.PassportElementErrorUnspecified`.
         data (:class:`telegram.PersonalDetails` | :class:`telegram.IdDocument` | \
             :class:`telegram.ResidentialAddress` | :obj:`str`):
             Optional. Decrypted or encrypted data, available for "personal_details", "passport",
@@ -112,8 +114,6 @@ class EncryptedPassportElement(TelegramObject):
             requested for "passport", "driver_license", "identity_card", "internal_passport",
             "utility_bill", "bank_statement", "rental_agreement", "passport_registration" and
             "temporary_registration" types.
-        hash (:obj:`str`): Base64-encoded element hash for using in
-            :class:`telegram.PassportElementErrorUnspecified`.
         bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
 
     """
@@ -135,6 +135,7 @@ class EncryptedPassportElement(TelegramObject):
     def __init__(
         self,
         type: str,  # pylint: disable=W0622
+        hash: str,  # pylint: disable=W0622
         data: PersonalDetails = None,
         phone_number: str = None,
         email: str = None,
@@ -143,7 +144,6 @@ def __init__(
         reverse_side: PassportFile = None,
         selfie: PassportFile = None,
         translation: List[PassportFile] = None,
-        hash: str = None,  # pylint: disable=W0622
         bot: 'Bot' = None,
         credentials: 'Credentials' = None,  # pylint: disable=W0613
         **_kwargs: Any,
diff --git a/telegram/passport/passportfile.py b/telegram/passport/passportfile.py
index b8356acf9b5..1731569aa7c 100644
--- a/telegram/passport/passportfile.py
+++ b/telegram/passport/passportfile.py
@@ -72,7 +72,7 @@ def __init__(
         file_id: str,
         file_unique_id: str,
         file_date: int,
-        file_size: int = None,
+        file_size: int,
         bot: 'Bot' = None,
         credentials: 'FileCredentials' = None,
         **_kwargs: Any,
diff --git a/telegram/voicechat.py b/telegram/voicechat.py
index c76553d5e2f..e4c2a819e25 100644
--- a/telegram/voicechat.py
+++ b/telegram/voicechat.py
@@ -40,7 +40,7 @@ class VoiceChatStarted(TelegramObject):
 
     __slots__ = ()
 
-    def __init__(self, **_kwargs: Any):  # skipcq: PTC-W0049
+    def __init__(self, **_kwargs: object):  # skipcq: PTC-W0049
         pass
 
 
@@ -66,7 +66,7 @@ class VoiceChatEnded(TelegramObject):
 
     __slots__ = ('duration',)
 
-    def __init__(self, duration: int, **_kwargs: Any) -> None:
+    def __init__(self, duration: int, **_kwargs: object) -> None:
         self.duration = int(duration) if duration is not None else None
         self._id_attrs = (self.duration,)
 
@@ -83,25 +83,22 @@ class VoiceChatParticipantsInvited(TelegramObject):
     .. versionadded:: 13.4
 
     Args:
-        users (List[:class:`telegram.User`]):  New members that
+        users (List[:class:`telegram.User`], optional):  New members that
             were invited to the voice chat.
         **kwargs (:obj:`dict`): Arbitrary keyword arguments.
 
     Attributes:
-        users (List[:class:`telegram.User`]):  New members that
+        users (List[:class:`telegram.User`]): Optional. New members that
             were invited to the voice chat.
 
     """
 
     __slots__ = ('users',)
 
-    def __init__(self, users: List[User], **_kwargs: Any) -> None:
+    def __init__(self, users: List[User] = None, **_kwargs: object) -> None:
         self.users = users
         self._id_attrs = (self.users,)
 
-    def __hash__(self) -> int:
-        return hash(tuple(self.users))
-
     @classmethod
     def de_json(
         cls, data: Optional[JSONDict], bot: 'Bot'
@@ -119,7 +116,8 @@ def to_dict(self) -> JSONDict:
         """See :meth:`telegram.TelegramObject.to_dict`."""
         data = super().to_dict()
 
-        data["users"] = [u.to_dict() for u in self.users]
+        if self.users is not None:
+            data["users"] = [u.to_dict() for u in self.users]
         return data
 
 
@@ -142,7 +140,7 @@ class VoiceChatScheduled(TelegramObject):
 
     __slots__ = ('start_date',)
 
-    def __init__(self, start_date: dtm.datetime, **_kwargs: Any) -> None:
+    def __init__(self, start_date: dtm.datetime, **_kwargs: object) -> None:
         self.start_date = start_date
 
         self._id_attrs = (self.start_date,)
diff --git a/tests/test_bot.py b/tests/test_bot.py
index 747c5a96cc6..44f79deac71 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -1313,7 +1313,7 @@ def assertion(url, data, *args, **kwargs):
 
         monkeypatch.setattr(bot.request, 'post', assertion)
 
-        assert bot.set_webhook(drop_pending_updates=drop_pending_updates)
+        assert bot.set_webhook('', drop_pending_updates=drop_pending_updates)
         assert bot.delete_webhook(drop_pending_updates=drop_pending_updates)
 
     @flaky(3, 1)
@@ -1779,7 +1779,6 @@ def test_set_chat_title(self, bot, channel_id):
     def test_set_chat_description(self, bot, channel_id):
         assert bot.set_chat_description(channel_id, 'Time: ' + str(time.time()))
 
-    # TODO: Add bot to group to test there too
     @flaky(3, 1)
     def test_pin_and_unpin_message(self, bot, super_group_id):
         message1 = bot.send_message(super_group_id, text="test_pin_message_1")
diff --git a/tests/test_chatmember.py b/tests/test_chatmember.py
index 62c296c37fb..3b04f0908f6 100644
--- a/tests/test_chatmember.py
+++ b/tests/test_chatmember.py
@@ -17,6 +17,7 @@
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 import datetime
+import inspect
 from copy import deepcopy
 
 import pytest
@@ -34,202 +35,197 @@
     Dice,
 )
 
-
-@pytest.fixture(scope='class')
-def user():
-    return User(1, 'First name', False)
-
-
-@pytest.fixture(
-    scope="class",
-    params=[
-        (ChatMemberOwner, ChatMember.CREATOR),
-        (ChatMemberAdministrator, ChatMember.ADMINISTRATOR),
-        (ChatMemberMember, ChatMember.MEMBER),
-        (ChatMemberRestricted, ChatMember.RESTRICTED),
-        (ChatMemberLeft, ChatMember.LEFT),
-        (ChatMemberBanned, ChatMember.KICKED),
-    ],
-    ids=[
-        ChatMember.CREATOR,
-        ChatMember.ADMINISTRATOR,
-        ChatMember.MEMBER,
-        ChatMember.RESTRICTED,
-        ChatMember.LEFT,
-        ChatMember.KICKED,
+ignored = ['self', '_kwargs']
+
+
+class CMDefaults:
+    user = User(1, 'First name', False)
+    custom_title: str = 'PTB'
+    is_anonymous: bool = True
+    until_date: datetime.datetime = to_timestamp(datetime.datetime.utcnow())
+    can_be_edited: bool = False
+    can_change_info: bool = True
+    can_post_messages: bool = True
+    can_edit_messages: bool = True
+    can_delete_messages: bool = True
+    can_invite_users: bool = True
+    can_restrict_members: bool = True
+    can_pin_messages: bool = True
+    can_promote_members: bool = True
+    can_send_messages: bool = True
+    can_send_media_messages: bool = True
+    can_send_polls: bool = True
+    can_send_other_messages: bool = True
+    can_add_web_page_previews: bool = True
+    is_member: bool = True
+    can_manage_chat: bool = True
+    can_manage_voice_chats: bool = True
+
+
+def chat_member_owner():
+    return ChatMemberOwner(CMDefaults.user, CMDefaults.is_anonymous, CMDefaults.custom_title)
+
+
+def chat_member_administrator():
+    return ChatMemberAdministrator(
+        CMDefaults.user,
+        CMDefaults.can_be_edited,
+        CMDefaults.is_anonymous,
+        CMDefaults.can_manage_chat,
+        CMDefaults.can_delete_messages,
+        CMDefaults.can_manage_voice_chats,
+        CMDefaults.can_restrict_members,
+        CMDefaults.can_promote_members,
+        CMDefaults.can_change_info,
+        CMDefaults.can_invite_users,
+        CMDefaults.can_post_messages,
+        CMDefaults.can_edit_messages,
+        CMDefaults.can_pin_messages,
+        CMDefaults.custom_title,
+    )
+
+
+def chat_member_member():
+    return ChatMemberMember(CMDefaults.user)
+
+
+def chat_member_restricted():
+    return ChatMemberRestricted(
+        CMDefaults.user,
+        CMDefaults.is_member,
+        CMDefaults.can_change_info,
+        CMDefaults.can_invite_users,
+        CMDefaults.can_pin_messages,
+        CMDefaults.can_send_messages,
+        CMDefaults.can_send_media_messages,
+        CMDefaults.can_send_polls,
+        CMDefaults.can_send_other_messages,
+        CMDefaults.can_add_web_page_previews,
+        CMDefaults.until_date,
+    )
+
+
+def chat_member_left():
+    return ChatMemberLeft(CMDefaults.user)
+
+
+def chat_member_banned():
+    return ChatMemberBanned(CMDefaults.user, CMDefaults.until_date)
+
+
+def make_json_dict(instance: ChatMember, include_optional_args: bool = False) -> dict:
+    """Used to make the json dict which we use for testing de_json. Similar to iter_args()"""
+    json_dict = {'status': instance.status}
+    sig = inspect.signature(instance.__class__.__init__)
+
+    for param in sig.parameters.values():
+        if param.name in ignored:  # ignore irrelevant params
+            continue
+
+        val = getattr(instance, param.name)
+        # Compulsory args-
+        if param.default is inspect.Parameter.empty:
+            if hasattr(val, 'to_dict'):  # convert the user object or any future ones to dict.
+                val = val.to_dict()
+            json_dict[param.name] = val
+
+        # If we want to test all args (for de_json)-
+        elif param.default is not inspect.Parameter.empty and include_optional_args:
+            json_dict[param.name] = val
+    return json_dict
+
+
+def iter_args(instance: ChatMember, de_json_inst: ChatMember, include_optional: bool = False):
+    """
+    We accept both the regular instance and de_json created instance and iterate over them for
+    easy one line testing later one.
+    """
+    yield instance.status, de_json_inst.status  # yield this here cause it's not available in sig.
+
+    sig = inspect.signature(instance.__class__.__init__)
+    for param in sig.parameters.values():
+        if param.name in ignored:
+            continue
+        inst_at, json_at = getattr(instance, param.name), getattr(de_json_inst, param.name)
+        if isinstance(json_at, datetime.datetime):  # Convert datetime to int
+            json_at = to_timestamp(json_at)
+        if param.default is not inspect.Parameter.empty and include_optional:
+            yield inst_at, json_at
+        elif param.default is inspect.Parameter.empty:
+            yield inst_at, json_at
+
+
+@pytest.fixture
+def chat_member_type(request):
+    return request.param()
+
+
+@pytest.mark.parametrize(
+    "chat_member_type",
+    [
+        chat_member_owner,
+        chat_member_administrator,
+        chat_member_member,
+        chat_member_restricted,
+        chat_member_left,
+        chat_member_banned,
     ],
+    indirect=True,
 )
-def chat_member_class_and_status(request):
-    return request.param
-
-
-@pytest.fixture(scope='class')
-def chat_member_types(chat_member_class_and_status, user):
-    return chat_member_class_and_status[0](status=chat_member_class_and_status[1], user=user)
-
-
-class TestChatMember:
-    def test_slot_behaviour(self, chat_member_types, mro_slots):
-        for attr in chat_member_types.__slots__:
-            assert getattr(chat_member_types, attr, 'err') != 'err', f"got extra slot '{attr}'"
-        assert len(mro_slots(chat_member_types)) == len(
-            set(mro_slots(chat_member_types))
-        ), "duplicate slot"
+class TestChatMemberTypes:
+    def test_slot_behaviour(self, chat_member_type, mro_slots):
+        inst = chat_member_type
+        for attr in inst.__slots__:
+            assert getattr(inst, attr, 'err') != 'err', f"got extra slot '{attr}'"
+        assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
+
+    def test_de_json_required_args(self, bot, chat_member_type):
+        cls = chat_member_type.__class__
+        assert cls.de_json({}, bot) is None
 
-    def test_de_json_required_args(self, bot, chat_member_class_and_status, user):
-        cls = chat_member_class_and_status[0]
-        status = chat_member_class_and_status[1]
+        json_dict = make_json_dict(chat_member_type)
+        const_chat_member = ChatMember.de_json(json_dict, bot)
 
-        assert cls.de_json({}, bot) is None
+        assert isinstance(const_chat_member, ChatMember)
+        assert isinstance(const_chat_member, cls)
+        for chat_mem_type_at, const_chat_mem_at in iter_args(chat_member_type, const_chat_member):
+            assert chat_mem_type_at == const_chat_mem_at
 
-        json_dict = {'status': status, 'user': user.to_dict()}
-        chat_member_type = ChatMember.de_json(json_dict, bot)
+    def test_de_json_all_args(self, bot, chat_member_type):
+        json_dict = make_json_dict(chat_member_type, include_optional_args=True)
+        const_chat_member = ChatMember.de_json(json_dict, bot)
 
-        assert isinstance(chat_member_type, ChatMember)
-        assert isinstance(chat_member_type, cls)
-        assert chat_member_type.status == status
-        assert chat_member_type.user == user
-
-    def test_de_json_all_args(self, bot, chat_member_class_and_status, user):
-        cls = chat_member_class_and_status[0]
-        status = chat_member_class_and_status[1]
-        time = datetime.datetime.utcnow()
-
-        json_dict = {
-            'user': user.to_dict(),
-            'status': status,
-            'custom_title': 'PTB',
-            'is_anonymous': True,
-            'until_date': to_timestamp(time),
-            'can_be_edited': False,
-            'can_change_info': True,
-            'can_post_messages': False,
-            'can_edit_messages': True,
-            'can_delete_messages': True,
-            'can_invite_users': False,
-            'can_restrict_members': True,
-            'can_pin_messages': False,
-            'can_promote_members': True,
-            'can_send_messages': False,
-            'can_send_media_messages': True,
-            'can_send_polls': False,
-            'can_send_other_messages': True,
-            'can_add_web_page_previews': False,
-            'can_manage_chat': True,
-            'can_manage_voice_chats': True,
-        }
-        chat_member_type = ChatMember.de_json(json_dict, bot)
+        assert isinstance(const_chat_member, ChatMember)
+        assert isinstance(const_chat_member, chat_member_type.__class__)
+        for c_mem_type_at, const_c_mem_at in iter_args(chat_member_type, const_chat_member, True):
+            assert c_mem_type_at == const_c_mem_at
 
-        assert isinstance(chat_member_type, ChatMember)
-        assert isinstance(chat_member_type, cls)
-        assert chat_member_type.user == user
-        assert chat_member_type.status == status
-        if chat_member_type.custom_title is not None:
-            assert chat_member_type.custom_title == 'PTB'
-            assert type(chat_member_type) in {ChatMemberOwner, ChatMemberAdministrator}
-        if chat_member_type.is_anonymous is not None:
-            assert chat_member_type.is_anonymous is True
-            assert type(chat_member_type) in {ChatMemberOwner, ChatMemberAdministrator}
-        if chat_member_type.until_date is not None:
-            assert type(chat_member_type) in {ChatMemberBanned, ChatMemberRestricted}
-        if chat_member_type.can_be_edited is not None:
-            assert chat_member_type.can_be_edited is False
-            assert type(chat_member_type) == ChatMemberAdministrator
-        if chat_member_type.can_change_info is not None:
-            assert chat_member_type.can_change_info is True
-            assert type(chat_member_type) in {ChatMemberAdministrator, ChatMemberRestricted}
-        if chat_member_type.can_post_messages is not None:
-            assert chat_member_type.can_post_messages is False
-            assert type(chat_member_type) == ChatMemberAdministrator
-        if chat_member_type.can_edit_messages is not None:
-            assert chat_member_type.can_edit_messages is True
-            assert type(chat_member_type) == ChatMemberAdministrator
-        if chat_member_type.can_delete_messages is not None:
-            assert chat_member_type.can_delete_messages is True
-            assert type(chat_member_type) == ChatMemberAdministrator
-        if chat_member_type.can_invite_users is not None:
-            assert chat_member_type.can_invite_users is False
-            assert type(chat_member_type) in {ChatMemberAdministrator, ChatMemberRestricted}
-        if chat_member_type.can_restrict_members is not None:
-            assert chat_member_type.can_restrict_members is True
-            assert type(chat_member_type) == ChatMemberAdministrator
-        if chat_member_type.can_pin_messages is not None:
-            assert chat_member_type.can_pin_messages is False
-            assert type(chat_member_type) in {ChatMemberAdministrator, ChatMemberRestricted}
-        if chat_member_type.can_promote_members is not None:
-            assert chat_member_type.can_promote_members is True
-            assert type(chat_member_type) == ChatMemberAdministrator
-        if chat_member_type.can_send_messages is not None:
-            assert chat_member_type.can_send_messages is False
-            assert type(chat_member_type) == ChatMemberRestricted
-        if chat_member_type.can_send_media_messages is not None:
-            assert chat_member_type.can_send_media_messages is True
-            assert type(chat_member_type) == ChatMemberRestricted
-        if chat_member_type.can_send_polls is not None:
-            assert chat_member_type.can_send_polls is False
-            assert type(chat_member_type) == ChatMemberRestricted
-        if chat_member_type.can_send_other_messages is not None:
-            assert chat_member_type.can_send_other_messages is True
-            assert type(chat_member_type) == ChatMemberRestricted
-        if chat_member_type.can_add_web_page_previews is not None:
-            assert chat_member_type.can_add_web_page_previews is False
-            assert type(chat_member_type) == ChatMemberRestricted
-        if chat_member_type.can_manage_chat is not None:
-            assert chat_member_type.can_manage_chat is True
-            assert type(chat_member_type) == ChatMemberAdministrator
-        if chat_member_type.can_manage_voice_chats is not None:
-            assert chat_member_type.can_manage_voice_chats is True
-            assert type(chat_member_type) == ChatMemberAdministrator
-
-    def test_de_json_invalid_status(self, bot, user):
-        json_dict = {'status': 'invalid', 'user': user.to_dict()}
+    def test_de_json_invalid_status(self, chat_member_type, bot):
+        json_dict = {'status': 'invalid', 'user': CMDefaults.user.to_dict()}
         chat_member_type = ChatMember.de_json(json_dict, bot)
 
         assert type(chat_member_type) is ChatMember
         assert chat_member_type.status == 'invalid'
 
-    def test_de_json_subclass(self, chat_member_class_and_status, bot, chat_id, user):
+    def test_de_json_subclass(self, chat_member_type, bot, chat_id):
         """This makes sure that e.g. ChatMemberAdministrator(data, bot) never returns a
-        ChatMemberKicked instance."""
-        cls = chat_member_class_and_status[0]
-        time = datetime.datetime.utcnow()
-        json_dict = {
-            'user': user.to_dict(),
-            'status': 'status',
-            'custom_title': 'PTB',
-            'is_anonymous': True,
-            'until_date': to_timestamp(time),
-            'can_be_edited': False,
-            'can_change_info': True,
-            'can_post_messages': False,
-            'can_edit_messages': True,
-            'can_delete_messages': True,
-            'can_invite_users': False,
-            'can_restrict_members': True,
-            'can_pin_messages': False,
-            'can_promote_members': True,
-            'can_send_messages': False,
-            'can_send_media_messages': True,
-            'can_send_polls': False,
-            'can_send_other_messages': True,
-            'can_add_web_page_previews': False,
-            'can_manage_chat': True,
-            'can_manage_voice_chats': True,
-        }
+        ChatMemberBanned instance."""
+        cls = chat_member_type.__class__
+        json_dict = make_json_dict(chat_member_type, True)
         assert type(cls.de_json(json_dict, bot)) is cls
 
-    def test_to_dict(self, chat_member_types, user):
-        chat_member_dict = chat_member_types.to_dict()
+    def test_to_dict(self, chat_member_type):
+        chat_member_dict = chat_member_type.to_dict()
 
         assert isinstance(chat_member_dict, dict)
-        assert chat_member_dict['status'] == chat_member_types.status
-        assert chat_member_dict['user'] == user.to_dict()
-
-    def test_equality(self, chat_member_types, user):
-        a = ChatMember(status='status', user=user)
-        b = ChatMember(status='status', user=user)
-        c = chat_member_types
-        d = deepcopy(chat_member_types)
+        assert chat_member_dict['status'] == chat_member_type.status
+        assert chat_member_dict['user'] == chat_member_type.user.to_dict()
+
+    def test_equality(self, chat_member_type):
+        a = ChatMember(status='status', user=CMDefaults.user)
+        b = ChatMember(status='status', user=CMDefaults.user)
+        c = chat_member_type
+        d = deepcopy(chat_member_type)
         e = Dice(4, 'emoji')
 
         assert a == b
diff --git a/tests/test_chatmemberupdated.py b/tests/test_chatmemberupdated.py
index 681be38edda..1a9ef5ce1bd 100644
--- a/tests/test_chatmemberupdated.py
+++ b/tests/test_chatmemberupdated.py
@@ -22,7 +22,14 @@
 import pytest
 import pytz
 
-from telegram import User, ChatMember, Chat, ChatMemberUpdated, ChatInviteLink
+from telegram import (
+    User,
+    ChatMember,
+    ChatMemberAdministrator,
+    Chat,
+    ChatMemberUpdated,
+    ChatInviteLink,
+)
 from telegram.utils.helpers import to_timestamp
 
 
@@ -43,7 +50,19 @@ def old_chat_member(user):
 
 @pytest.fixture(scope='class')
 def new_chat_member(user):
-    return ChatMember(user, TestChatMemberUpdated.new_status)
+    return ChatMemberAdministrator(
+        user,
+        TestChatMemberUpdated.new_status,
+        True,
+        True,
+        True,
+        True,
+        True,
+        True,
+        True,
+        True,
+        True,
+    )
 
 
 @pytest.fixture(scope='class')
diff --git a/tests/test_encryptedpassportelement.py b/tests/test_encryptedpassportelement.py
index 225496ee453..4aea27ec4c3 100644
--- a/tests/test_encryptedpassportelement.py
+++ b/tests/test_encryptedpassportelement.py
@@ -26,6 +26,7 @@
 def encrypted_passport_element():
     return EncryptedPassportElement(
         TestEncryptedPassportElement.type_,
+        'we missed this',
         data=TestEncryptedPassportElement.data,
         phone_number=TestEncryptedPassportElement.phone_number,
         email=TestEncryptedPassportElement.email,
@@ -38,13 +39,14 @@ def encrypted_passport_element():
 
 class TestEncryptedPassportElement:
     type_ = 'type'
+    hash = 'we missed this'
     data = 'data'
     phone_number = 'phone_number'
     email = 'email'
-    files = [PassportFile('file_id', 50, 0)]
-    front_side = PassportFile('file_id', 50, 0)
-    reverse_side = PassportFile('file_id', 50, 0)
-    selfie = PassportFile('file_id', 50, 0)
+    files = [PassportFile('file_id', 50, 0, 25)]
+    front_side = PassportFile('file_id', 50, 0, 25)
+    reverse_side = PassportFile('file_id', 50, 0, 25)
+    selfie = PassportFile('file_id', 50, 0, 25)
 
     def test_slot_behaviour(self, encrypted_passport_element, mro_slots):
         inst = encrypted_passport_element
@@ -54,6 +56,7 @@ def test_slot_behaviour(self, encrypted_passport_element, mro_slots):
 
     def test_expected_values(self, encrypted_passport_element):
         assert encrypted_passport_element.type == self.type_
+        assert encrypted_passport_element.hash == self.hash
         assert encrypted_passport_element.data == self.data
         assert encrypted_passport_element.phone_number == self.phone_number
         assert encrypted_passport_element.email == self.email
@@ -88,8 +91,8 @@ def test_to_dict(self, encrypted_passport_element):
         )
 
     def test_equality(self):
-        a = EncryptedPassportElement(self.type_, data=self.data)
-        b = EncryptedPassportElement(self.type_, data=self.data)
+        a = EncryptedPassportElement(self.type_, '12313123', data=self.data)
+        b = EncryptedPassportElement(self.type_, '12313123', data=self.data)
         c = EncryptedPassportElement(self.data, '')
         d = PassportElementError('source', 'type', 'message')
 
diff --git a/tests/test_inputmedia.py b/tests/test_inputmedia.py
index 582e0a223d5..f01fb6e493f 100644
--- a/tests/test_inputmedia.py
+++ b/tests/test_inputmedia.py
@@ -638,9 +638,9 @@ def build_media(parse_mode, med_type):
         message = default_bot.send_photo(chat_id, photo)
 
         message = default_bot.edit_message_media(
+            build_media(parse_mode=ParseMode.HTML, med_type=media_type),
             message.chat_id,
             message.message_id,
-            media=build_media(parse_mode=ParseMode.HTML, med_type=media_type),
         )
         assert message.caption == test_caption
         assert message.caption_entities == test_entities
@@ -649,9 +649,9 @@ def build_media(parse_mode, med_type):
         message.edit_caption()
 
         message = default_bot.edit_message_media(
+            build_media(parse_mode=ParseMode.MARKDOWN_V2, med_type=media_type),
             message.chat_id,
             message.message_id,
-            media=build_media(parse_mode=ParseMode.MARKDOWN_V2, med_type=media_type),
         )
         assert message.caption == test_caption
         assert message.caption_entities == test_entities
@@ -660,9 +660,9 @@ def build_media(parse_mode, med_type):
         message.edit_caption()
 
         message = default_bot.edit_message_media(
+            build_media(parse_mode=None, med_type=media_type),
             message.chat_id,
             message.message_id,
-            media=build_media(parse_mode=None, med_type=media_type),
         )
         assert message.caption == markdown_caption
         assert message.caption_entities == []
diff --git a/tests/test_official.py b/tests/test_official.py
index 5217d4e6932..ec7c97e1085 100644
--- a/tests/test_official.py
+++ b/tests/test_official.py
@@ -18,6 +18,7 @@
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 import os
 import inspect
+from typing import List
 
 import certifi
 import pytest
@@ -40,6 +41,13 @@
     'kwargs',
 }
 
+ignored_param_requirements = {
+    'send_location': {'latitude', 'longitude'},
+    'edit_message_live_location': {'latitude', 'longitude'},
+    'send_venue': {'latitude', 'longitude', 'title', 'address'},
+    'send_contact': {'phone_number', 'first_name'},
+}
+
 
 def find_next_sibling_until(tag, name, until):
     for sibling in tag.next_siblings:
@@ -49,7 +57,8 @@ def find_next_sibling_until(tag, name, until):
             return sibling
 
 
-def parse_table(h4):
+def parse_table(h4) -> List[List[str]]:
+    """Parses the Telegram doc table and has an output of a 2D list."""
     table = find_next_sibling_until(h4, 'table', h4.find_next_sibling('h4'))
     if not table:
         return []
@@ -60,8 +69,8 @@ def parse_table(h4):
 
 
 def check_method(h4):
-    name = h4.text
-    method = getattr(telegram.Bot, name)
+    name = h4.text  # name of the method in telegram's docs.
+    method = getattr(telegram.Bot, name)  # Retrieve our lib method
     table = parse_table(h4)
 
     # Check arguments based on source
@@ -71,8 +80,10 @@ def check_method(h4):
     for parameter in table:
         param = sig.parameters.get(parameter[0])
         assert param is not None, f"Parameter {parameter[0]} not found in {method.__name__}"
-        # TODO: Check type via docstring
-        # TODO: Check if optional or required
+
+        assert check_required_params(
+            parameter, param.name, sig, method.__name__
+        ), f'Param {param.name!r} of method {method.__name__!r} requirement mismatch!'
         checked.append(parameter[0])
 
     ignored = IGNORED_PARAMETERS.copy()
@@ -133,8 +144,9 @@ def check_object(h4):
 
         param = sig.parameters.get(field)
         assert param is not None, f"Attribute {field} not found in {obj.__name__}"
-        # TODO: Check type via docstring
-        # TODO: Check if optional or required
+        assert check_required_params(
+            parameter, field, sig, obj.__name__
+        ), f"{obj.__name__!r} parameter {param.name!r} requirement mismatch"
         checked.append(field)
 
     ignored = IGNORED_PARAMETERS.copy()
@@ -181,6 +193,26 @@ def check_object(h4):
     assert (sig.parameters.keys() ^ checked) - ignored == set()
 
 
+def check_required_params(
+    param_desc: List[str], param_name: str, sig: inspect.Signature, method_name: str
+) -> bool:
+    """Checks if the method/class parameter is a required/optional param as per Telegram docs."""
+    if len(param_desc) == 4:  # this means that there is a dedicated 'Required' column present.
+        # Handle cases where we provide convenience intentionally-
+        if param_name in ignored_param_requirements.get(method_name, {}):
+            return True
+        is_required = True if param_desc[2] in {'Required', 'Yes'} else False
+        is_ours_required = sig.parameters[param_name].default is inspect.Signature.empty
+        return is_required is is_ours_required
+
+    if len(param_desc) == 3:  # The docs mention the requirement in the description for classes...
+        if param_name == 'force_reply':  # edge case
+            return True
+        is_required = False if param_desc[2].split('.', 1)[0] == 'Optional' else True
+        is_ours_required = sig.parameters[param_name].default is inspect.Signature.empty
+        return is_required is is_ours_required
+
+
 argvalues = []
 names = []
 http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())
diff --git a/tests/test_passport.py b/tests/test_passport.py
index eeeb574ecb3..2b86ed3b296 100644
--- a/tests/test_passport.py
+++ b/tests/test_passport.py
@@ -47,9 +47,11 @@
         {
             'data': 'QRfzWcCN4WncvRO3lASG+d+c5gzqXtoCinQ1PgtYiZMKXCksx9eB9Ic1bOt8C/un9/XaX220PjJSO7Kuba+nXXC51qTsjqP9rnLKygnEIWjKrfiDdklzgcukpRzFSjiOAvhy86xFJZ1PfPSrFATy/Gp1RydLzbrBd2ZWxZqXrxcMoA0Q2UTTFXDoCYerEAiZoD69i79tB/6nkLBcUUvN5d52gKd/GowvxWqAAmdO6l1N7jlo6aWjdYQNBAK1KHbJdbRZMJLxC1MqMuZXAYrPoYBRKr5xAnxDTmPn/LEZKLc3gwwZyEgR5x7e9jp5heM6IEMmsv3O/6SUeEQs7P0iVuRSPLMJLfDdwns8Tl3fF2M4IxKVovjCaOVW+yHKsADDAYQPzzH2RcrWVD0TP5I64mzpK64BbTOq3qm3Hn51SV9uA/+LvdGbCp7VnzHx4EdUizHsVyilJULOBwvklsrDRvXMiWmh34ZSR6zilh051tMEcRf0I+Oe7pIxVJd/KKfYA2Z/eWVQTCn5gMuAInQNXFSqDIeIqBX+wca6kvOCUOXB7J2uRjTpLaC4DM9s/sNjSBvFixcGAngt+9oap6Y45rQc8ZJaNN/ALqEJAmkphW8=',
             'type': 'personal_details',
+            'hash': 'What to put here?',
         },
         {
             'reverse_side': {
+                'file_size': 32424112,
                 'file_date': 1534074942,
                 'file_id': 'DgADBAADNQQAAtoagFPf4wwmFZdmyQI',
                 'file_unique_id': 'adc3145fd2e84d95b64d68eaa22aa33e',
@@ -82,6 +84,7 @@
                 'file_unique_id': 'd4e390cca57b4da5a65322b304762a12',
             },
             'data': 'eJUOFuY53QKmGqmBgVWlLBAQCUQJ79n405SX6M5aGFIIodOPQqnLYvMNqTwTrXGDlW+mVLZcbu+y8luLVO8WsJB/0SB7q5WaXn/IMt1G9lz5G/KMLIZG/x9zlnimsaQLg7u8srG6L4KZzv+xkbbHjZdETrxU8j0N/DoS4HvLMRSJAgeFUrY6v2YW9vSRg+fSxIqQy1jR2VKpzAT8OhOz7A==',
+            'hash': 'We seriously need to improve this mess! took so long to debug!',
         },
         {
             'translation': [
@@ -113,12 +116,14 @@
                 },
             ],
             'type': 'utility_bill',
+            'hash': 'Wow over 30 minutes spent debugging passport stuff.',
         },
         {
             'data': 'j9SksVkSj128DBtZA+3aNjSFNirzv+R97guZaMgae4Gi0oDVNAF7twPR7j9VSmPedfJrEwL3O889Ei+a5F1xyLLyEI/qEBljvL70GFIhYGitS0JmNabHPHSZrjOl8b4s/0Z0Px2GpLO5siusTLQonimdUvu4UPjKquYISmlKEKhtmGATy+h+JDjNCYuOkhakeNw0Rk0BHgj0C3fCb7WZNQSyVb+2GTu6caR6eXf/AFwFp0TV3sRz3h0WIVPW8bna',
             'type': 'address',
+            'hash': 'at least I get the pattern now',
         },
-        {'email': 'fb3e3i47zt@dispostable.com', 'type': 'email'},
+        {'email': 'fb3e3i47zt@dispostable.com', 'type': 'email', 'hash': 'this should be it.'},
     ],
 }
 
@@ -126,13 +131,18 @@
 @pytest.fixture(scope='function')
 def all_passport_data():
     return [
-        {'type': 'personal_details', 'data': RAW_PASSPORT_DATA['data'][0]['data']},
+        {
+            'type': 'personal_details',
+            'data': RAW_PASSPORT_DATA['data'][0]['data'],
+            'hash': 'what to put here?',
+        },
         {
             'type': 'passport',
             'data': RAW_PASSPORT_DATA['data'][1]['data'],
             'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
             'selfie': RAW_PASSPORT_DATA['data'][1]['selfie'],
             'translation': RAW_PASSPORT_DATA['data'][1]['translation'],
+            'hash': 'more data arghh',
         },
         {
             'type': 'internal_passport',
@@ -140,6 +150,7 @@ def all_passport_data():
             'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
             'selfie': RAW_PASSPORT_DATA['data'][1]['selfie'],
             'translation': RAW_PASSPORT_DATA['data'][1]['translation'],
+            'hash': 'more data arghh',
         },
         {
             'type': 'driver_license',
@@ -148,6 +159,7 @@ def all_passport_data():
             'reverse_side': RAW_PASSPORT_DATA['data'][1]['reverse_side'],
             'selfie': RAW_PASSPORT_DATA['data'][1]['selfie'],
             'translation': RAW_PASSPORT_DATA['data'][1]['translation'],
+            'hash': 'more data arghh',
         },
         {
             'type': 'identity_card',
@@ -156,35 +168,49 @@ def all_passport_data():
             'reverse_side': RAW_PASSPORT_DATA['data'][1]['reverse_side'],
             'selfie': RAW_PASSPORT_DATA['data'][1]['selfie'],
             'translation': RAW_PASSPORT_DATA['data'][1]['translation'],
+            'hash': 'more data arghh',
         },
         {
             'type': 'utility_bill',
             'files': RAW_PASSPORT_DATA['data'][2]['files'],
             'translation': RAW_PASSPORT_DATA['data'][2]['translation'],
+            'hash': 'more data arghh',
         },
         {
             'type': 'bank_statement',
             'files': RAW_PASSPORT_DATA['data'][2]['files'],
             'translation': RAW_PASSPORT_DATA['data'][2]['translation'],
+            'hash': 'more data arghh',
         },
         {
             'type': 'rental_agreement',
             'files': RAW_PASSPORT_DATA['data'][2]['files'],
             'translation': RAW_PASSPORT_DATA['data'][2]['translation'],
+            'hash': 'more data arghh',
         },
         {
             'type': 'passport_registration',
             'files': RAW_PASSPORT_DATA['data'][2]['files'],
             'translation': RAW_PASSPORT_DATA['data'][2]['translation'],
+            'hash': 'more data arghh',
         },
         {
             'type': 'temporary_registration',
             'files': RAW_PASSPORT_DATA['data'][2]['files'],
             'translation': RAW_PASSPORT_DATA['data'][2]['translation'],
+            'hash': 'more data arghh',
+        },
+        {
+            'type': 'address',
+            'data': RAW_PASSPORT_DATA['data'][3]['data'],
+            'hash': 'more data arghh',
+        },
+        {'type': 'email', 'email': 'fb3e3i47zt@dispostable.com', 'hash': 'more data arghh'},
+        {
+            'type': 'phone_number',
+            'phone_number': 'fb3e3i47zt@dispostable.com',
+            'hash': 'more data arghh',
         },
-        {'type': 'address', 'data': RAW_PASSPORT_DATA['data'][3]['data']},
-        {'type': 'email', 'email': 'fb3e3i47zt@dispostable.com'},
-        {'type': 'phone_number', 'phone_number': 'fb3e3i47zt@dispostable.com'},
     ]
 
 
diff --git a/tests/test_update.py b/tests/test_update.py
index e095541d132..a02aa56ca04 100644
--- a/tests/test_update.py
+++ b/tests/test_update.py
@@ -33,7 +33,7 @@
     Poll,
     PollOption,
     ChatMemberUpdated,
-    ChatMember,
+    ChatMemberOwner,
 )
 from telegram.poll import PollAnswer
 from telegram.utils.helpers import from_timestamp
@@ -43,8 +43,8 @@
     Chat(1, 'chat'),
     User(1, '', False),
     from_timestamp(int(time.time())),
-    ChatMember(User(1, '', False), ChatMember.CREATOR),
-    ChatMember(User(1, '', False), ChatMember.CREATOR),
+    ChatMemberOwner(User(1, '', False), True),
+    ChatMemberOwner(User(1, '', False), True),
 )
 
 params = [
diff --git a/tests/test_voicechat.py b/tests/test_voicechat.py
index 94174bb4183..2da9d436b2e 100644
--- a/tests/test_voicechat.py
+++ b/tests/test_voicechat.py
@@ -95,7 +95,7 @@ def test_equality(self):
 
 
 class TestVoiceChatParticipantsInvited:
-    def test_slot_behaviour(self, mro_slots):
+    def test_slot_behaviour(self, mro_slots, user1):
         action = VoiceChatParticipantsInvited([user1])
         for attr in action.__slots__:
             assert getattr(action, attr, 'err') != 'err', f"got extra slot '{attr}'"
@@ -120,25 +120,6 @@ def test_to_dict(self, user1, user2):
         assert voice_chat_dict["users"][0]["id"] == user1.id
         assert voice_chat_dict["users"][1]["id"] == user2.id
 
-    def test_equality(self, user1, user2):
-        a = VoiceChatParticipantsInvited([user1])
-        b = VoiceChatParticipantsInvited([user1])
-        c = VoiceChatParticipantsInvited([user1, user2])
-        d = VoiceChatParticipantsInvited([user2])
-        e = VoiceChatStarted()
-
-        assert a == b
-        assert hash(a) == hash(b)
-
-        assert a != c
-        assert hash(a) != hash(c)
-
-        assert a != d
-        assert hash(a) != hash(d)
-
-        assert a != e
-        assert hash(a) != hash(e)
-
 
 class TestVoiceChatScheduled:
     start_date = dtm.datetime.utcnow()


Code review comments:
diff_hunk: @@ -120,25 +120,6 @@ def test_to_dict(self, user1, user2):
         assert voice_chat_dict["users"][0]["id"] == user1.id
         assert voice_chat_dict["users"][1]["id"] == user2.id
 
-    def test_equality(self, user1, user2):
-        a = VoiceChatParticipantsInvited([user1])
-        b = VoiceChatParticipantsInvited([user1])
-        c = VoiceChatParticipantsInvited([user1, user2])
-        d = VoiceChatParticipantsInvited([user2])
-        e = VoiceChatStarted()
-
-        assert a == b
-        assert hash(a) == hash(b)
-
-        assert a != c
-        assert hash(a) != hash(c)
-
-        assert a != d
-        assert hash(a) != hash(d)
-
-        assert a != e
-        assert hash(a) != hash(e)
-
path: tests/test_voicechat.py
text: @user1:
see above. equality should still be tested, even if we remove `__hash__`, IG …

diff_hunk: @@ -26,6 +26,7 @@
 def encrypted_passport_element():
     return EncryptedPassportElement(
         TestEncryptedPassportElement.type_,
+        'we missed this',
path: tests/test_encryptedpassportelement.py
text: @user1:
maybe more understandable in the future 😄 
```suggestion
        'this is a hash',
```