Repository: python-telegram-bot/python-telegram-bot

Issue:
Split Tests for Classes with Subclass-`de_json`
Basically #4593 for all other classes where `de_json` returns subclasses.
Doing this first will make #4635 easier for those classes.

# Canditates
(all subclasse of `TelegramObject` that have subclasses on their own)

- [ ] BackgroundFill #4654 
- [ ] BackgroundType #4654 
- [ ] BotCommandScope #4654 
- [ ] ChatBoostSource #4654 
- [ ] ChatMember #4654 
- [x] MaybeInaccessibleMessage
- [ ] MenuButton #4654 
- [ ] PaidMedia #4654 
- [ ] ReactionType #4654 
- [x] RevenueWithdrawalState
- [x] TransactionPartner
- [x] InputMedia doesn't use `de_json` because input-only class
- [x] InputPaidMedia doesn't use `de_json` because input-only class
- [x] InlineQueryResult doesn't use `de_json` because input-only class
- [x] InputMessageContent doesn't use `de_json` because input-only class
- [x] PassportElementError


Discussion:

Patch:
diff --git a/telegram/_chatmember.py b/telegram/_chatmember.py
index d9f256aa2c1..647c089edde 100644
--- a/telegram/_chatmember.py
+++ b/telegram/_chatmember.py
@@ -24,6 +24,7 @@
 from telegram import constants
 from telegram._telegramobject import TelegramObject
 from telegram._user import User
+from telegram._utils import enum
 from telegram._utils.argumentparsing import de_json_optional
 from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
 from telegram._utils.types import JSONDict
@@ -99,7 +100,7 @@ def __init__(
         super().__init__(api_kwargs=api_kwargs)
         # Required by all subclasses
         self.user: User = user
-        self.status: str = status
+        self.status: str = enum.get_member(constants.ChatMemberStatus, status, status)
 
         self._id_attrs = (self.user, self.status)
 


Test patch:
diff --git a/tests/test_botcommandscope.py b/tests/test_botcommandscope.py
index e1ae9f585c4..9f4b10a800a 100644
--- a/tests/test_botcommandscope.py
+++ b/tests/test_botcommandscope.py
@@ -16,7 +16,6 @@
 #
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
-from copy import deepcopy
 
 import pytest
 
@@ -35,147 +34,336 @@
 from tests.auxil.slots import mro_slots
 
 
-@pytest.fixture(scope="module", params=["str", "int"])
-def chat_id(request):
-    if request.param == "str":
-        return "@supergroupusername"
-    return 43
-
-
-@pytest.fixture(
-    scope="class",
-    params=[
-        BotCommandScope.DEFAULT,
-        BotCommandScope.ALL_PRIVATE_CHATS,
-        BotCommandScope.ALL_GROUP_CHATS,
-        BotCommandScope.ALL_CHAT_ADMINISTRATORS,
-        BotCommandScope.CHAT,
-        BotCommandScope.CHAT_ADMINISTRATORS,
-        BotCommandScope.CHAT_MEMBER,
-    ],
-)
-def scope_type(request):
-    return request.param
-
-
-@pytest.fixture(
-    scope="module",
-    params=[
-        BotCommandScopeDefault,
-        BotCommandScopeAllPrivateChats,
-        BotCommandScopeAllGroupChats,
-        BotCommandScopeAllChatAdministrators,
-        BotCommandScopeChat,
-        BotCommandScopeChatAdministrators,
-        BotCommandScopeChatMember,
-    ],
-    ids=[
-        BotCommandScope.DEFAULT,
-        BotCommandScope.ALL_PRIVATE_CHATS,
-        BotCommandScope.ALL_GROUP_CHATS,
-        BotCommandScope.ALL_CHAT_ADMINISTRATORS,
-        BotCommandScope.CHAT,
-        BotCommandScope.CHAT_ADMINISTRATORS,
-        BotCommandScope.CHAT_MEMBER,
-    ],
-)
-def scope_class(request):
-    return request.param
-
-
-@pytest.fixture(
-    scope="module",
-    params=[
-        (BotCommandScopeDefault, BotCommandScope.DEFAULT),
-        (BotCommandScopeAllPrivateChats, BotCommandScope.ALL_PRIVATE_CHATS),
-        (BotCommandScopeAllGroupChats, BotCommandScope.ALL_GROUP_CHATS),
-        (BotCommandScopeAllChatAdministrators, BotCommandScope.ALL_CHAT_ADMINISTRATORS),
-        (BotCommandScopeChat, BotCommandScope.CHAT),
-        (BotCommandScopeChatAdministrators, BotCommandScope.CHAT_ADMINISTRATORS),
-        (BotCommandScopeChatMember, BotCommandScope.CHAT_MEMBER),
-    ],
-    ids=[
-        BotCommandScope.DEFAULT,
-        BotCommandScope.ALL_PRIVATE_CHATS,
-        BotCommandScope.ALL_GROUP_CHATS,
-        BotCommandScope.ALL_CHAT_ADMINISTRATORS,
-        BotCommandScope.CHAT,
-        BotCommandScope.CHAT_ADMINISTRATORS,
-        BotCommandScope.CHAT_MEMBER,
-    ],
-)
-def scope_class_and_type(request):
-    return request.param
+@pytest.fixture
+def bot_command_scope():
+    return BotCommandScope(BotCommandScopeTestBase.type)
 
 
-@pytest.fixture(scope="module")
-def bot_command_scope(scope_class_and_type, chat_id):
-    # we use de_json here so that we don't have to worry about which class needs which arguments
-    return scope_class_and_type[0].de_json(
-        {"type": scope_class_and_type[1], "chat_id": chat_id, "user_id": 42}, bot=None
-    )
+class BotCommandScopeTestBase:
+    type = BotCommandScopeType.DEFAULT
+    chat_id = 123456789
+    user_id = 987654321
 
 
-# All the scope types are very similar, so we test everything via parametrization
-class TestBotCommandScopeWithoutRequest:
+class TestBotCommandScopeWithoutRequest(BotCommandScopeTestBase):
     def test_slot_behaviour(self, bot_command_scope):
-        for attr in bot_command_scope.__slots__:
-            assert getattr(bot_command_scope, attr, "err") != "err", f"got extra slot '{attr}'"
-        assert len(mro_slots(bot_command_scope)) == len(
-            set(mro_slots(bot_command_scope))
-        ), "duplicate slot"
-
-    def test_de_json(self, offline_bot, scope_class_and_type, chat_id):
-        cls = scope_class_and_type[0]
-        type_ = scope_class_and_type[1]
-
-        json_dict = {"type": type_, "chat_id": chat_id, "user_id": 42}
-        bot_command_scope = BotCommandScope.de_json(json_dict, offline_bot)
-        assert set(bot_command_scope.api_kwargs.keys()) == {"chat_id", "user_id"} - set(
-            cls.__slots__
+        inst = bot_command_scope
+        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_type_enum_conversion(self, bot_command_scope):
+        assert type(BotCommandScope("default").type) is BotCommandScopeType
+        assert BotCommandScope("unknown").type == "unknown"
+
+    def test_de_json(self, offline_bot):
+        data = {"type": "unknown"}
+        transaction_partner = BotCommandScope.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "unknown"
+
+    @pytest.mark.parametrize(
+        ("bcs_type", "subclass"),
+        [
+            ("all_private_chats", BotCommandScopeAllPrivateChats),
+            ("all_chat_administrators", BotCommandScopeAllChatAdministrators),
+            ("all_group_chats", BotCommandScopeAllGroupChats),
+            ("chat", BotCommandScopeChat),
+            ("chat_administrators", BotCommandScopeChatAdministrators),
+            ("chat_member", BotCommandScopeChatMember),
+            ("default", BotCommandScopeDefault),
+        ],
+    )
+    def test_de_json_subclass(self, offline_bot, bcs_type, subclass):
+        json_dict = {
+            "type": bcs_type,
+            "chat_id": self.chat_id,
+            "user_id": self.user_id,
+        }
+        bcs = BotCommandScope.de_json(json_dict, offline_bot)
+
+        assert type(bcs) is subclass
+        assert set(bcs.api_kwargs.keys()) == set(json_dict.keys()) - set(subclass.__slots__) - {
+            "type"
+        }
+        assert bcs.type == bcs_type
+
+    def test_to_dict(self, bot_command_scope):
+        data = bot_command_scope.to_dict()
+        assert data == {"type": "default"}
+
+    def test_equality(self, bot_command_scope):
+        a = bot_command_scope
+        b = BotCommandScope(self.type)
+        c = BotCommandScope("unknown")
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def bot_command_scope_all_private_chats():
+    return BotCommandScopeAllPrivateChats()
+
+
+class TestBotCommandScopeAllPrivateChatsWithoutRequest(BotCommandScopeTestBase):
+    type = BotCommandScopeType.ALL_PRIVATE_CHATS
+
+    def test_slot_behaviour(self, bot_command_scope_all_private_chats):
+        inst = bot_command_scope_all_private_chats
+        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(self, offline_bot):
+        transaction_partner = BotCommandScopeAllPrivateChats.de_json({}, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "all_private_chats"
+
+    def test_to_dict(self, bot_command_scope_all_private_chats):
+        assert bot_command_scope_all_private_chats.to_dict() == {
+            "type": bot_command_scope_all_private_chats.type
+        }
+
+    def test_equality(self, bot_command_scope_all_private_chats):
+        a = bot_command_scope_all_private_chats
+        b = BotCommandScopeAllPrivateChats()
+        c = Dice(5, "test")
+        d = BotCommandScopeDefault()
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def bot_command_scope_all_chat_administrators():
+    return BotCommandScopeAllChatAdministrators()
+
+
+class TestBotCommandScopeAllChatAdministratorsWithoutRequest(BotCommandScopeTestBase):
+    type = BotCommandScopeType.ALL_CHAT_ADMINISTRATORS
+
+    def test_slot_behaviour(self, bot_command_scope_all_chat_administrators):
+        inst = bot_command_scope_all_chat_administrators
+        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(self, offline_bot):
+        transaction_partner = BotCommandScopeAllChatAdministrators.de_json({}, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "all_chat_administrators"
+
+    def test_to_dict(self, bot_command_scope_all_chat_administrators):
+        assert bot_command_scope_all_chat_administrators.to_dict() == {
+            "type": bot_command_scope_all_chat_administrators.type
+        }
+
+    def test_equality(self, bot_command_scope_all_chat_administrators):
+        a = bot_command_scope_all_chat_administrators
+        b = BotCommandScopeAllChatAdministrators()
+        c = Dice(5, "test")
+        d = BotCommandScopeDefault()
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def bot_command_scope_all_group_chats():
+    return BotCommandScopeAllGroupChats()
+
+
+class TestBotCommandScopeAllGroupChatsWithoutRequest(BotCommandScopeTestBase):
+    type = BotCommandScopeType.ALL_GROUP_CHATS
+
+    def test_slot_behaviour(self, bot_command_scope_all_group_chats):
+        inst = bot_command_scope_all_group_chats
+        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(self, offline_bot):
+        transaction_partner = BotCommandScopeAllGroupChats.de_json({}, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "all_group_chats"
+
+    def test_to_dict(self, bot_command_scope_all_group_chats):
+        assert bot_command_scope_all_group_chats.to_dict() == {
+            "type": bot_command_scope_all_group_chats.type
+        }
+
+    def test_equality(self, bot_command_scope_all_group_chats):
+        a = bot_command_scope_all_group_chats
+        b = BotCommandScopeAllGroupChats()
+        c = Dice(5, "test")
+        d = BotCommandScopeDefault()
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def bot_command_scope_chat():
+    return BotCommandScopeChat(TestBotCommandScopeChatWithoutRequest.chat_id)
+
+
+class TestBotCommandScopeChatWithoutRequest(BotCommandScopeTestBase):
+    type = BotCommandScopeType.CHAT
+
+    def test_slot_behaviour(self, bot_command_scope_chat):
+        inst = bot_command_scope_chat
+        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(self, offline_bot):
+        transaction_partner = BotCommandScopeChat.de_json({"chat_id": self.chat_id}, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "chat"
+        assert transaction_partner.chat_id == self.chat_id
+
+    def test_to_dict(self, bot_command_scope_chat):
+        assert bot_command_scope_chat.to_dict() == {
+            "type": bot_command_scope_chat.type,
+            "chat_id": self.chat_id,
+        }
+
+    def test_equality(self, bot_command_scope_chat):
+        a = bot_command_scope_chat
+        b = BotCommandScopeChat(self.chat_id)
+        c = BotCommandScopeChat(self.chat_id + 1)
+        d = Dice(5, "test")
+        e = BotCommandScopeDefault()
+
+        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)
+
+
+@pytest.fixture
+def bot_command_scope_chat_administrators():
+    return BotCommandScopeChatAdministrators(
+        TestBotCommandScopeChatAdministratorsWithoutRequest.chat_id
+    )
+
+
+class TestBotCommandScopeChatAdministratorsWithoutRequest(BotCommandScopeTestBase):
+    type = BotCommandScopeType.CHAT_ADMINISTRATORS
+
+    def test_slot_behaviour(self, bot_command_scope_chat_administrators):
+        inst = bot_command_scope_chat_administrators
+        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(self, offline_bot):
+        transaction_partner = BotCommandScopeChatAdministrators.de_json(
+            {"chat_id": self.chat_id}, offline_bot
         )
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "chat_administrators"
+        assert transaction_partner.chat_id == self.chat_id
+
+    def test_to_dict(self, bot_command_scope_chat_administrators):
+        assert bot_command_scope_chat_administrators.to_dict() == {
+            "type": bot_command_scope_chat_administrators.type,
+            "chat_id": self.chat_id,
+        }
+
+    def test_equality(self, bot_command_scope_chat_administrators):
+        a = bot_command_scope_chat_administrators
+        b = BotCommandScopeChatAdministrators(self.chat_id)
+        c = BotCommandScopeChatAdministrators(self.chat_id + 1)
+        d = Dice(5, "test")
+        e = BotCommandScopeDefault()
 
-        assert isinstance(bot_command_scope, BotCommandScope)
-        assert isinstance(bot_command_scope, cls)
-        assert bot_command_scope.type == type_
-        if "chat_id" in cls.__slots__:
-            assert bot_command_scope.chat_id == chat_id
-        if "user_id" in cls.__slots__:
-            assert bot_command_scope.user_id == 42
+        assert a == b
+        assert hash(a) == hash(b)
 
-    def test_de_json_invalid_type(self, offline_bot):
-        json_dict = {"type": "invalid", "chat_id": chat_id, "user_id": 42}
-        bot_command_scope = BotCommandScope.de_json(json_dict, offline_bot)
+        assert a != c
+        assert hash(a) != hash(c)
 
-        assert type(bot_command_scope) is BotCommandScope
-        assert bot_command_scope.type == "invalid"
+        assert a != d
+        assert hash(a) != hash(d)
 
-    def test_de_json_subclass(self, scope_class, offline_bot, chat_id):
-        """This makes sure that e.g. BotCommandScopeDefault(data) never returns a
-        BotCommandScopeChat instance."""
-        json_dict = {"type": "invalid", "chat_id": chat_id, "user_id": 42}
-        assert type(scope_class.de_json(json_dict, offline_bot)) is scope_class
+        assert a != e
+        assert hash(a) != hash(e)
 
-    def test_to_dict(self, bot_command_scope):
-        bot_command_scope_dict = bot_command_scope.to_dict()
 
-        assert isinstance(bot_command_scope_dict, dict)
-        assert bot_command_scope["type"] == bot_command_scope.type
-        if hasattr(bot_command_scope, "chat_id"):
-            assert bot_command_scope["chat_id"] == bot_command_scope.chat_id
-        if hasattr(bot_command_scope, "user_id"):
-            assert bot_command_scope["user_id"] == bot_command_scope.user_id
+@pytest.fixture
+def bot_command_scope_chat_member():
+    return BotCommandScopeChatMember(
+        TestBotCommandScopeChatMemberWithoutRequest.chat_id,
+        TestBotCommandScopeChatMemberWithoutRequest.user_id,
+    )
 
-    def test_type_enum_conversion(self):
-        assert type(BotCommandScope("default").type) is BotCommandScopeType
-        assert BotCommandScope("unknown").type == "unknown"
 
-    def test_equality(self, bot_command_scope, offline_bot):
-        a = BotCommandScope("base_type")
-        b = BotCommandScope("base_type")
-        c = bot_command_scope
-        d = deepcopy(bot_command_scope)
-        e = Dice(4, "emoji")
+class TestBotCommandScopeChatMemberWithoutRequest(BotCommandScopeTestBase):
+    type = BotCommandScopeType.CHAT_MEMBER
+
+    def test_slot_behaviour(self, bot_command_scope_chat_member):
+        inst = bot_command_scope_chat_member
+        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(self, offline_bot):
+        transaction_partner = BotCommandScopeChatMember.de_json(
+            {"chat_id": self.chat_id, "user_id": self.user_id}, offline_bot
+        )
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "chat_member"
+        assert transaction_partner.chat_id == self.chat_id
+        assert transaction_partner.user_id == self.user_id
+
+    def test_to_dict(self, bot_command_scope_chat_member):
+        assert bot_command_scope_chat_member.to_dict() == {
+            "type": bot_command_scope_chat_member.type,
+            "chat_id": self.chat_id,
+            "user_id": self.user_id,
+        }
+
+    def test_equality(self, bot_command_scope_chat_member):
+        a = bot_command_scope_chat_member
+        b = BotCommandScopeChatMember(self.chat_id, self.user_id)
+        c = BotCommandScopeChatMember(self.chat_id + 1, self.user_id)
+        d = BotCommandScopeChatMember(self.chat_id, self.user_id + 1)
+        e = Dice(5, "test")
+        f = BotCommandScopeDefault()
 
         assert a == b
         assert hash(a) == hash(b)
@@ -189,24 +377,43 @@ def test_equality(self, bot_command_scope, offline_bot):
         assert a != e
         assert hash(a) != hash(e)
 
-        assert c == d
-        assert hash(c) == hash(d)
+        assert a != f
+        assert hash(a) != hash(f)
+
 
-        assert c != e
-        assert hash(c) != hash(e)
+@pytest.fixture
+def bot_command_scope_default():
+    return BotCommandScopeDefault()
 
-        if hasattr(c, "chat_id"):
-            json_dict = c.to_dict()
-            json_dict["chat_id"] = 0
-            f = c.__class__.de_json(json_dict, offline_bot)
 
-            assert c != f
-            assert hash(c) != hash(f)
+class TestBotCommandScopeDefaultWithoutRequest(BotCommandScopeTestBase):
+    type = BotCommandScopeType.DEFAULT
 
-        if hasattr(c, "user_id"):
-            json_dict = c.to_dict()
-            json_dict["user_id"] = 0
-            g = c.__class__.de_json(json_dict, offline_bot)
+    def test_slot_behaviour(self, bot_command_scope_default):
+        inst = bot_command_scope_default
+        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"
 
-            assert c != g
-            assert hash(c) != hash(g)
+    def test_de_json(self, offline_bot):
+        transaction_partner = BotCommandScopeDefault.de_json({}, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "default"
+
+    def test_to_dict(self, bot_command_scope_default):
+        assert bot_command_scope_default.to_dict() == {"type": bot_command_scope_default.type}
+
+    def test_equality(self, bot_command_scope_default):
+        a = bot_command_scope_default
+        b = BotCommandScopeDefault()
+        c = Dice(5, "test")
+        d = BotCommandScopeChatMember(123, 456)
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
diff --git a/tests/test_chatbackground.py b/tests/test_chatbackground.py
index f1dd40b9325..7af6e8d2f7f 100644
--- a/tests/test_chatbackground.py
+++ b/tests/test_chatbackground.py
@@ -16,9 +16,6 @@
 #
 # 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 inspect
-from copy import deepcopy
-from typing import Union
 
 import pytest
 
@@ -32,204 +29,306 @@
     BackgroundTypeFill,
     BackgroundTypePattern,
     BackgroundTypeWallpaper,
+    ChatBackground,
     Dice,
     Document,
 )
+from telegram.constants import BackgroundFillType, BackgroundTypeType
 from tests.auxil.slots import mro_slots
 
-ignored = ["self", "api_kwargs"]
+
+@pytest.fixture
+def background_fill():
+    return BackgroundFill(BackgroundFillTestBase.type)
 
 
-class BFDefaults:
-    color = 0
-    top_color = 1
-    bottom_color = 2
+class BackgroundFillTestBase:
+    type = BackgroundFill.SOLID
+    color = 42
+    top_color = 43
+    bottom_color = 44
     rotation_angle = 45
-    colors = [0, 1, 2]
+    colors = [46, 47, 48, 49]
 
 
-def background_fill_solid():
-    return BackgroundFillSolid(BFDefaults.color)
+class TestBackgroundFillWithoutRequest(BackgroundFillTestBase):
+    def test_slots(self, background_fill):
+        inst = background_fill
+        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_type_enum_conversion(self, background_fill):
+        assert type(BackgroundFill("solid").type) is BackgroundFillType
+        assert BackgroundFill("unknown").type == "unknown"
+
+    def test_de_json(self, offline_bot):
+        data = {"type": "unknown"}
+        transaction_partner = BackgroundFill.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "unknown"
+
+    @pytest.mark.parametrize(
+        ("bf_type", "subclass"),
+        [
+            ("solid", BackgroundFillSolid),
+            ("gradient", BackgroundFillGradient),
+            ("freeform_gradient", BackgroundFillFreeformGradient),
+        ],
+    )
+    def test_de_json_subclass(self, offline_bot, bf_type, subclass):
+        json_dict = {
+            "type": bf_type,
+            "color": self.color,
+            "top_color": self.top_color,
+            "bottom_color": self.bottom_color,
+            "rotation_angle": self.rotation_angle,
+            "colors": self.colors,
+        }
+        bf = BackgroundFill.de_json(json_dict, offline_bot)
+
+        assert type(bf) is subclass
+        assert set(bf.api_kwargs.keys()) == set(json_dict.keys()) - set(subclass.__slots__) - {
+            "type"
+        }
+        assert bf.type == bf_type
+
+    def test_to_dict(self, background_fill):
+        assert background_fill.to_dict() == {"type": background_fill.type}
+
+    def test_equality(self, background_fill):
+        a = background_fill
+        b = BackgroundFill(self.type)
+        c = BackgroundFill("unknown")
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
 
 
+@pytest.fixture
 def background_fill_gradient():
     return BackgroundFillGradient(
-        BFDefaults.top_color, BFDefaults.bottom_color, BFDefaults.rotation_angle
+        TestBackgroundFillGradientWithoutRequest.top_color,
+        TestBackgroundFillGradientWithoutRequest.bottom_color,
+        TestBackgroundFillGradientWithoutRequest.rotation_angle,
     )
 
 
-def background_fill_freeform_gradient():
-    return BackgroundFillFreeformGradient(BFDefaults.colors)
+class TestBackgroundFillGradientWithoutRequest(BackgroundFillTestBase):
+    type = BackgroundFill.GRADIENT
+
+    def test_slots(self, background_fill_gradient):
+        inst = background_fill_gradient
+        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(self, offline_bot):
+        data = {
+            "top_color": self.top_color,
+            "bottom_color": self.bottom_color,
+            "rotation_angle": self.rotation_angle,
+        }
+        transaction_partner = BackgroundFillGradient.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "gradient"
+
+    def test_to_dict(self, background_fill_gradient):
+        assert background_fill_gradient.to_dict() == {
+            "type": background_fill_gradient.type,
+            "top_color": self.top_color,
+            "bottom_color": self.bottom_color,
+            "rotation_angle": self.rotation_angle,
+        }
+
+    def test_equality(self, background_fill_gradient):
+        a = background_fill_gradient
+        b = BackgroundFillGradient(
+            self.top_color,
+            self.bottom_color,
+            self.rotation_angle,
+        )
+        c = BackgroundFillGradient(
+            self.top_color + 1,
+            self.bottom_color + 1,
+            self.rotation_angle + 1,
+        )
+        d = Dice(5, "test")
 
-class BTDefaults:
-    document = Document(1, 2)
-    fill = BackgroundFillSolid(color=0)
-    dark_theme_dimming = 20
-    is_blurred = True
-    is_moving = False
-    intensity = 90
-    is_inverted = False
-    theme_name = "ice"
+        assert a == b
+        assert hash(a) == hash(b)
 
+        assert a != c
+        assert hash(a) != hash(c)
 
-def background_type_fill():
-    return BackgroundTypeFill(BTDefaults.fill, BTDefaults.dark_theme_dimming)
+        assert a != d
+        assert hash(a) != hash(d)
 
 
-def background_type_wallpaper():
-    return BackgroundTypeWallpaper(
-        BTDefaults.document,
-        BTDefaults.dark_theme_dimming,
-        BTDefaults.is_blurred,
-        BTDefaults.is_moving,
+@pytest.fixture
+def background_fill_freeform_gradient():
+    return BackgroundFillFreeformGradient(
+        TestBackgroundFillFreeformGradientWithoutRequest.colors,
     )
 
 
-def background_type_pattern():
-    return BackgroundTypePattern(
-        BTDefaults.document,
-        BTDefaults.fill,
-        BTDefaults.intensity,
-        BTDefaults.is_inverted,
-        BTDefaults.is_moving,
-    )
+class TestBackgroundFillFreeformGradientWithoutRequest(BackgroundFillTestBase):
+    type = BackgroundFill.FREEFORM_GRADIENT
+
+    def test_slots(self, background_fill_freeform_gradient):
+        inst = background_fill_freeform_gradient
+        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(self, offline_bot):
+        data = {"colors": self.colors}
+        transaction_partner = BackgroundFillFreeformGradient.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "freeform_gradient"
 
-def background_type_chat_theme():
-    return BackgroundTypeChatTheme(BTDefaults.theme_name)
-
-
-def make_json_dict(
-    instance: Union[BackgroundType, BackgroundFill], 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 = {"type": instance.type}
-    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: Union[BackgroundType, BackgroundFill],
-    de_json_inst: Union[BackgroundType, BackgroundFill],
-    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.type, de_json_inst.type  # 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 (
-            param.default is not inspect.Parameter.empty and include_optional
-        ) or param.default is inspect.Parameter.empty:
-            yield inst_at, json_at
+    def test_to_dict(self, background_fill_freeform_gradient):
+        assert background_fill_freeform_gradient.to_dict() == {
+            "type": background_fill_freeform_gradient.type,
+            "colors": self.colors,
+        }
+
+    def test_equality(self, background_fill_freeform_gradient):
+        a = background_fill_freeform_gradient
+        b = BackgroundFillFreeformGradient(self.colors)
+        c = BackgroundFillFreeformGradient([color + 1 for color in self.colors])
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
 
 
 @pytest.fixture
-def background_type(request):
-    return request.param()
-
-
-@pytest.mark.parametrize(
-    "background_type",
-    [
-        background_type_fill,
-        background_type_wallpaper,
-        background_type_pattern,
-        background_type_chat_theme,
-    ],
-    indirect=True,
-)
-class TestBackgroundTypeWithoutRequest:
-    def test_slot_behaviour(self, background_type):
-        inst = background_type
+def background_fill_solid():
+    return BackgroundFillSolid(TestBackgroundFillSolidWithoutRequest.color)
+
+
+class TestBackgroundFillSolidWithoutRequest(BackgroundFillTestBase):
+    type = BackgroundFill.SOLID
+
+    def test_slots(self, background_fill_solid):
+        inst = background_fill_solid
         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, offline_bot, background_type):
-        cls = background_type.__class__
+    def test_de_json(self, offline_bot):
+        data = {"color": self.color}
+        transaction_partner = BackgroundFillSolid.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "solid"
+
+    def test_to_dict(self, background_fill_solid):
+        assert background_fill_solid.to_dict() == {
+            "type": background_fill_solid.type,
+            "color": self.color,
+        }
 
-        json_dict = make_json_dict(background_type)
-        const_background_type = BackgroundType.de_json(json_dict, offline_bot)
-        assert const_background_type.api_kwargs == {}
+    def test_equality(self, background_fill_solid):
+        a = background_fill_solid
+        b = BackgroundFillSolid(self.color)
+        c = BackgroundFillSolid(self.color + 1)
+        d = Dice(5, "test")
 
-        assert isinstance(const_background_type, BackgroundType)
-        assert isinstance(const_background_type, cls)
-        for bg_type_at, const_bg_type_at in iter_args(background_type, const_background_type):
-            assert bg_type_at == const_bg_type_at
+        assert a == b
+        assert hash(a) == hash(b)
 
-    def test_de_json_all_args(self, offline_bot, background_type):
-        json_dict = make_json_dict(background_type, include_optional_args=True)
-        const_background_type = BackgroundType.de_json(json_dict, offline_bot)
+        assert a != c
+        assert hash(a) != hash(c)
 
-        assert const_background_type.api_kwargs == {}
+        assert a != d
+        assert hash(a) != hash(d)
 
-        assert isinstance(const_background_type, BackgroundType)
-        assert isinstance(const_background_type, background_type.__class__)
-        for bg_type_at, const_bg_type_at in iter_args(
-            background_type, const_background_type, True
-        ):
-            assert bg_type_at == const_bg_type_at
 
-    def test_de_json_invalid_type(self, background_type, offline_bot):
-        json_dict = {"type": "invalid", "theme_name": BTDefaults.theme_name}
-        background_type = BackgroundType.de_json(json_dict, offline_bot)
+@pytest.fixture
+def background_type():
+    return BackgroundType(BackgroundTypeTestBase.type)
 
-        assert type(background_type) is BackgroundType
-        assert background_type.type == "invalid"
 
-    def test_de_json_subclass(self, background_type, offline_bot, chat_id):
-        """This makes sure that e.g. BackgroundTypeFill(data, offline_bot) never returns a
-        BackgroundTypeWallpaper instance."""
-        cls = background_type.__class__
-        json_dict = make_json_dict(background_type, True)
-        assert type(cls.de_json(json_dict, offline_bot)) is cls
+class BackgroundTypeTestBase:
+    type = BackgroundType.WALLPAPER
+    fill = BackgroundFillSolid(42)
+    dark_theme_dimming = 43
+    document = Document("file_id", "file_unique_id", "file_name", 42)
+    is_blurred = True
+    is_moving = True
+    intensity = 45
+    is_inverted = True
+    theme_name = "test theme name"
 
-    def test_to_dict(self, background_type):
-        bg_type_dict = background_type.to_dict()
 
-        assert isinstance(bg_type_dict, dict)
-        assert bg_type_dict["type"] == background_type.type
+class TestBackgroundTypeWithoutRequest(BackgroundTypeTestBase):
+    def test_slots(self, background_type):
+        inst = background_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"
 
-        for slot in background_type.__slots__:  # additional verification for the optional args
-            if slot in ("fill", "document"):
-                assert (getattr(background_type, slot)).to_dict() == bg_type_dict[slot]
-                continue
-            assert getattr(background_type, slot) == bg_type_dict[slot]
+    def test_type_enum_conversion(self, background_type):
+        assert type(BackgroundType("wallpaper").type) is BackgroundTypeType
+        assert BackgroundType("unknown").type == "unknown"
+
+    def test_de_json(self, offline_bot):
+        data = {"type": "unknown"}
+        transaction_partner = BackgroundType.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "unknown"
+
+    @pytest.mark.parametrize(
+        ("bt_type", "subclass"),
+        [
+            ("wallpaper", BackgroundTypeWallpaper),
+            ("fill", BackgroundTypeFill),
+            ("pattern", BackgroundTypePattern),
+            ("chat_theme", BackgroundTypeChatTheme),
+        ],
+    )
+    def test_de_json_subclass(self, offline_bot, bt_type, subclass):
+        json_dict = {
+            "type": bt_type,
+            "fill": self.fill.to_dict(),
+            "dark_theme_dimming": self.dark_theme_dimming,
+            "document": self.document.to_dict(),
+            "is_blurred": self.is_blurred,
+            "is_moving": self.is_moving,
+            "intensity": self.intensity,
+            "is_inverted": self.is_inverted,
+            "theme_name": self.theme_name,
+        }
+        bt = BackgroundType.de_json(json_dict, offline_bot)
+
+        assert type(bt) is subclass
+        assert set(bt.api_kwargs.keys()) == set(json_dict.keys()) - set(subclass.__slots__) - {
+            "type"
+        }
+        assert bt.type == bt_type
+
+    def test_to_dict(self, background_type):
+        assert background_type.to_dict() == {"type": background_type.type}
 
     def test_equality(self, background_type):
-        a = BackgroundType(type="type")
-        b = BackgroundType(type="type")
-        c = background_type
-        d = deepcopy(background_type)
-        e = Dice(4, "emoji")
-        sig = inspect.signature(background_type.__class__.__init__)
-        params = [
-            "random" for param in sig.parameters.values() if param.name not in [*ignored, "type"]
-        ]
-        f = background_type.__class__(*params)
+        a = background_type
+        b = BackgroundType(self.type)
+        c = BackgroundType("unknown")
+        d = Dice(5, "test")
 
         assert a == b
         assert hash(a) == hash(b)
@@ -240,102 +339,218 @@ def test_equality(self, background_type):
         assert a != d
         assert hash(a) != hash(d)
 
-        assert a != e
-        assert hash(a) != hash(e)
 
-        assert c == d
-        assert hash(c) == hash(d)
+@pytest.fixture
+def background_type_fill():
+    return BackgroundTypeFill(
+        fill=TestBackgroundTypeFillWithoutRequest.fill,
+        dark_theme_dimming=TestBackgroundTypeFillWithoutRequest.dark_theme_dimming,
+    )
+
+
+class TestBackgroundTypeFillWithoutRequest(BackgroundTypeTestBase):
+    type = BackgroundType.FILL
+
+    def test_slots(self, background_type_fill):
+        inst = background_type_fill
+        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(self, offline_bot):
+        data = {"fill": self.fill.to_dict(), "dark_theme_dimming": self.dark_theme_dimming}
+        transaction_partner = BackgroundTypeFill.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "fill"
+
+    def test_to_dict(self, background_type_fill):
+        assert background_type_fill.to_dict() == {
+            "type": background_type_fill.type,
+            "fill": self.fill.to_dict(),
+            "dark_theme_dimming": self.dark_theme_dimming,
+        }
+
+    def test_equality(self, background_type_fill):
+        a = background_type_fill
+        b = BackgroundTypeFill(self.fill, self.dark_theme_dimming)
+        c = BackgroundTypeFill(BackgroundFillSolid(43), 44)
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
 
-        assert c != e
-        assert hash(c) != hash(e)
+        assert a != c
+        assert hash(a) != hash(c)
 
-        assert f != c
-        assert hash(f) != hash(c)
+        assert a != d
+        assert hash(a) != hash(d)
 
 
 @pytest.fixture
-def background_fill(request):
-    return request.param()
-
-
-@pytest.mark.parametrize(
-    "background_fill",
-    [
-        background_fill_solid,
-        background_fill_gradient,
-        background_fill_freeform_gradient,
-    ],
-    indirect=True,
-)
-class TestBackgroundFillWithoutRequest:
-    def test_slot_behaviour(self, background_fill):
-        inst = background_fill
+def background_type_pattern():
+    return BackgroundTypePattern(
+        TestBackgroundTypePatternWithoutRequest.document,
+        TestBackgroundTypePatternWithoutRequest.fill,
+        TestBackgroundTypePatternWithoutRequest.intensity,
+        TestBackgroundTypePatternWithoutRequest.is_inverted,
+        TestBackgroundTypePatternWithoutRequest.is_moving,
+    )
+
+
+class TestBackgroundTypePatternWithoutRequest(BackgroundTypeTestBase):
+    type = BackgroundType.PATTERN
+
+    def test_slots(self, background_type_pattern):
+        inst = background_type_pattern
         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, offline_bot, background_fill):
-        cls = background_fill.__class__
+    def test_de_json(self, offline_bot):
+        data = {
+            "document": self.document.to_dict(),
+            "fill": self.fill.to_dict(),
+            "intensity": self.intensity,
+            "is_inverted": self.is_inverted,
+            "is_moving": self.is_moving,
+        }
+        transaction_partner = BackgroundTypePattern.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "pattern"
+
+    def test_to_dict(self, background_type_pattern):
+        assert background_type_pattern.to_dict() == {
+            "type": background_type_pattern.type,
+            "document": self.document.to_dict(),
+            "fill": self.fill.to_dict(),
+            "intensity": self.intensity,
+            "is_inverted": self.is_inverted,
+            "is_moving": self.is_moving,
+        }
+
+    def test_equality(self, background_type_pattern):
+        a = background_type_pattern
+        b = BackgroundTypePattern(
+            self.document,
+            self.fill,
+            self.intensity,
+        )
+        c = BackgroundTypePattern(
+            Document("other", "other", "file_name", 43),
+            False,
+            False,
+            44,
+        )
+        d = Dice(5, "test")
 
-        json_dict = make_json_dict(background_fill)
-        const_background_fill = BackgroundFill.de_json(json_dict, offline_bot)
-        assert const_background_fill.api_kwargs == {}
+        assert a == b
+        assert hash(a) == hash(b)
 
-        assert isinstance(const_background_fill, BackgroundFill)
-        assert isinstance(const_background_fill, cls)
-        for bg_fill_at, const_bg_fill_at in iter_args(background_fill, const_background_fill):
-            assert bg_fill_at == const_bg_fill_at
+        assert a != c
+        assert hash(a) != hash(c)
 
-    def test_de_json_all_args(self, offline_bot, background_fill):
-        json_dict = make_json_dict(background_fill, include_optional_args=True)
-        const_background_fill = BackgroundFill.de_json(json_dict, offline_bot)
+        assert a != d
+        assert hash(a) != hash(d)
 
-        assert const_background_fill.api_kwargs == {}
 
-        assert isinstance(const_background_fill, BackgroundFill)
-        assert isinstance(const_background_fill, background_fill.__class__)
-        for bg_fill_at, const_bg_fill_at in iter_args(
-            background_fill, const_background_fill, True
-        ):
-            assert bg_fill_at == const_bg_fill_at
+@pytest.fixture
+def background_type_chat_theme():
+    return BackgroundTypeChatTheme(
+        TestBackgroundTypeChatThemeWithoutRequest.theme_name,
+    )
 
-    def test_de_json_invalid_type(self, background_fill, offline_bot):
-        json_dict = {"type": "invalid", "theme_name": BTDefaults.theme_name}
-        background_fill = BackgroundFill.de_json(json_dict, offline_bot)
 
-        assert type(background_fill) is BackgroundFill
-        assert background_fill.type == "invalid"
+class TestBackgroundTypeChatThemeWithoutRequest(BackgroundTypeTestBase):
+    type = BackgroundType.CHAT_THEME
 
-    def test_de_json_subclass(self, background_fill, offline_bot):
-        """This makes sure that e.g. BackgroundFillSolid(data, offline_bot) never returns a
-        BackgroundFillGradient instance."""
-        cls = background_fill.__class__
-        json_dict = make_json_dict(background_fill, True)
-        assert type(cls.de_json(json_dict, offline_bot)) is cls
+    def test_slots(self, background_type_chat_theme):
+        inst = background_type_chat_theme
+        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_to_dict(self, background_fill):
-        bg_fill_dict = background_fill.to_dict()
+    def test_de_json(self, offline_bot):
+        data = {"theme_name": self.theme_name}
+        transaction_partner = BackgroundTypeChatTheme.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "chat_theme"
 
-        assert isinstance(bg_fill_dict, dict)
-        assert bg_fill_dict["type"] == background_fill.type
+    def test_to_dict(self, background_type_chat_theme):
+        assert background_type_chat_theme.to_dict() == {
+            "type": background_type_chat_theme.type,
+            "theme_name": self.theme_name,
+        }
 
-        for slot in background_fill.__slots__:  # additional verification for the optional args
-            if slot == "colors":
-                assert getattr(background_fill, slot) == tuple(bg_fill_dict[slot])
-                continue
-            assert getattr(background_fill, slot) == bg_fill_dict[slot]
+    def test_equality(self, background_type_chat_theme):
+        a = background_type_chat_theme
+        b = BackgroundTypeChatTheme(self.theme_name)
+        c = BackgroundTypeChatTheme("other")
+        d = Dice(5, "test")
 
-    def test_equality(self, background_fill):
-        a = BackgroundFill(type="type")
-        b = BackgroundFill(type="type")
-        c = background_fill
-        d = deepcopy(background_fill)
-        e = Dice(4, "emoji")
-        sig = inspect.signature(background_fill.__class__.__init__)
-        params = [
-            "random" for param in sig.parameters.values() if param.name not in [*ignored, "type"]
-        ]
-        f = background_fill.__class__(*params)
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def background_type_wallpaper():
+    return BackgroundTypeWallpaper(
+        TestBackgroundTypeWallpaperWithoutRequest.document,
+        TestBackgroundTypeWallpaperWithoutRequest.dark_theme_dimming,
+        TestBackgroundTypeWallpaperWithoutRequest.is_blurred,
+        TestBackgroundTypeWallpaperWithoutRequest.is_moving,
+    )
+
+
+class TestBackgroundTypeWallpaperWithoutRequest(BackgroundTypeTestBase):
+    type = BackgroundType.WALLPAPER
+
+    def test_slots(self, background_type_wallpaper):
+        inst = background_type_wallpaper
+        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(self, offline_bot):
+        data = {
+            "document": self.document.to_dict(),
+            "dark_theme_dimming": self.dark_theme_dimming,
+            "is_blurred": self.is_blurred,
+            "is_moving": self.is_moving,
+        }
+        transaction_partner = BackgroundTypeWallpaper.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "wallpaper"
+
+    def test_to_dict(self, background_type_wallpaper):
+        assert background_type_wallpaper.to_dict() == {
+            "type": background_type_wallpaper.type,
+            "document": self.document.to_dict(),
+            "dark_theme_dimming": self.dark_theme_dimming,
+            "is_blurred": self.is_blurred,
+            "is_moving": self.is_moving,
+        }
+
+    def test_equality(self, background_type_wallpaper):
+        a = background_type_wallpaper
+        b = BackgroundTypeWallpaper(
+            self.document,
+            self.dark_theme_dimming,
+            self.is_blurred,
+            self.is_moving,
+        )
+        c = BackgroundTypeWallpaper(
+            Document("other", "other", "file_name", 43),
+            44,
+            False,
+            False,
+        )
+        d = Dice(5, "test")
 
         assert a == b
         assert hash(a) == hash(b)
@@ -346,14 +561,43 @@ def test_equality(self, background_fill):
         assert a != d
         assert hash(a) != hash(d)
 
-        assert a != e
-        assert hash(a) != hash(e)
 
-        assert c == d
-        assert hash(c) == hash(d)
+@pytest.fixture
+def chat_background():
+    return ChatBackground(ChatBackgroundTestBase.type)
+
+
+class ChatBackgroundTestBase:
+    type = BackgroundTypeFill(BackgroundFillSolid(42), 43)
+
+
+class TestChatBackgroundWithoutRequest(ChatBackgroundTestBase):
+    def test_slots(self, chat_background):
+        inst = chat_background
+        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(self, offline_bot):
+        data = {"type": self.type.to_dict()}
+        transaction_partner = ChatBackground.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == self.type
 
-        assert c != e
-        assert hash(c) != hash(e)
+    def test_to_dict(self, chat_background):
+        assert chat_background.to_dict() == {"type": chat_background.type.to_dict()}
 
-        assert f != c
-        assert hash(f) != hash(c)
+    def test_equality(self, chat_background):
+        a = chat_background
+        b = ChatBackground(self.type)
+        c = ChatBackground(BackgroundTypeFill(BackgroundFillSolid(43), 44))
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
diff --git a/tests/test_chatboost.py b/tests/test_chatboost.py
index 0440a0ff44c..ac4e06d0495 100644
--- a/tests/test_chatboost.py
+++ b/tests/test_chatboost.py
@@ -16,8 +16,6 @@
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
 
 import datetime as dtm
-import inspect
-from copy import deepcopy
 
 import pytest
 
@@ -43,194 +41,175 @@
 
 
 class ChatBoostDefaults:
+    source = ChatBoostSource.PREMIUM
     chat_id = 1
     boost_id = "2"
     giveaway_message_id = 3
     is_unclaimed = False
     chat = Chat(1, "group")
     user = User(1, "user", False)
-    date = to_timestamp(dtm.datetime.utcnow())
+    date = dtm.datetime.now(dtm.timezone.utc).replace(microsecond=0)
     default_source = ChatBoostSourcePremium(user)
     prize_star_count = 99
+    boost = ChatBoost(
+        boost_id=boost_id,
+        add_date=date,
+        expiration_date=date,
+        source=default_source,
+    )
 
 
 @pytest.fixture(scope="module")
-def chat_boost_removed():
-    return ChatBoostRemoved(
-        chat=ChatBoostDefaults.chat,
-        boost_id=ChatBoostDefaults.boost_id,
-        remove_date=ChatBoostDefaults.date,
-        source=ChatBoostDefaults.default_source,
+def chat_boost_source():
+    return ChatBoostSource(
+        source=ChatBoostDefaults.source,
     )
 
 
-@pytest.fixture(scope="module")
-def chat_boost():
-    return ChatBoost(
-        boost_id=ChatBoostDefaults.boost_id,
-        add_date=ChatBoostDefaults.date,
-        expiration_date=ChatBoostDefaults.date,
-        source=ChatBoostDefaults.default_source,
-    )
+class TestChatBoostSourceWithoutRequest(ChatBoostDefaults):
+    def test_slot_behaviour(self, chat_boost_source):
+        inst = chat_boost_source
+        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_type_enum_conversion(self, chat_boost_source):
+        assert type(ChatBoostSource("premium").source) is ChatBoostSources
+        assert ChatBoostSource("unknown").source == "unknown"
 
-@pytest.fixture(scope="module")
-def chat_boost_updated(chat_boost):
-    return ChatBoostUpdated(
-        chat=ChatBoostDefaults.chat,
-        boost=chat_boost,
+    def test_de_json(self, offline_bot):
+        json_dict = {
+            "source": "unknown",
+        }
+        cbs = ChatBoostSource.de_json(json_dict, offline_bot)
+
+        assert cbs.api_kwargs == {}
+        assert cbs.source == "unknown"
+
+    @pytest.mark.parametrize(
+        ("cb_source", "subclass"),
+        [
+            ("premium", ChatBoostSourcePremium),
+            ("gift_code", ChatBoostSourceGiftCode),
+            ("giveaway", ChatBoostSourceGiveaway),
+        ],
     )
+    def test_de_json_subclass(self, offline_bot, cb_source, subclass):
+        json_dict = {
+            "source": cb_source,
+            "user": ChatBoostDefaults.user.to_dict(),
+            "giveaway_message_id": ChatBoostDefaults.giveaway_message_id,
+        }
+        cbs = ChatBoostSource.de_json(json_dict, offline_bot)
 
+        assert type(cbs) is subclass
+        assert set(cbs.api_kwargs.keys()) == set(json_dict.keys()) - set(subclass.__slots__) - {
+            "source"
+        }
+        assert cbs.source == cb_source
 
-def chat_boost_source_gift_code():
-    return ChatBoostSourceGiftCode(
-        user=ChatBoostDefaults.user,
-    )
+    def test_to_dict(self, chat_boost_source):
+        chat_boost_source_dict = chat_boost_source.to_dict()
 
+        assert isinstance(chat_boost_source_dict, dict)
+        assert chat_boost_source_dict["source"] == chat_boost_source.source
 
-def chat_boost_source_giveaway():
-    return ChatBoostSourceGiveaway(
-        user=ChatBoostDefaults.user,
-        giveaway_message_id=ChatBoostDefaults.giveaway_message_id,
-        is_unclaimed=ChatBoostDefaults.is_unclaimed,
-        prize_star_count=ChatBoostDefaults.prize_star_count,
-    )
+    def test_equality(self, chat_boost_source):
+        a = chat_boost_source
+        b = ChatBoostSource(source=ChatBoostDefaults.source)
+        c = ChatBoostSource(source="unknown")
+        d = Dice(5, "test")
 
+        assert a == b
+        assert hash(a) == hash(b)
 
-def chat_boost_source_premium():
-    return ChatBoostSourcePremium(
-        user=ChatBoostDefaults.user,
-    )
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
 
 
 @pytest.fixture(scope="module")
-def user_chat_boosts(chat_boost):
-    return UserChatBoosts(
-        boosts=[chat_boost],
+def chat_boost_source_premium():
+    return ChatBoostSourcePremium(
+        user=TestChatBoostSourcePremiumWithoutRequest.user,
     )
 
 
-@pytest.fixture
-def chat_boost_source(request):
-    return request.param()
+class TestChatBoostSourcePremiumWithoutRequest(ChatBoostDefaults):
+    source = ChatBoostSources.PREMIUM
 
+    def test_slot_behaviour(self, chat_boost_source_premium):
+        inst = chat_boost_source_premium
+        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"
 
-ignored = ["self", "api_kwargs"]
+    def test_de_json(self, offline_bot):
+        json_dict = {
+            "user": self.user.to_dict(),
+        }
+        cbsp = ChatBoostSourcePremium.de_json(json_dict, offline_bot)
 
+        assert cbsp.api_kwargs == {}
+        assert cbsp.user == self.user
 
-def make_json_dict(instance: ChatBoostSource, 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 = {"source": instance.source}
-    sig = inspect.signature(instance.__class__.__init__)
+    def test_to_dict(self, chat_boost_source_premium):
+        chat_boost_source_premium_dict = chat_boost_source_premium.to_dict()
 
-    for param in sig.parameters.values():
-        if param.name in ignored:  # ignore irrelevant params
-            continue
+        assert isinstance(chat_boost_source_premium_dict, dict)
+        assert chat_boost_source_premium_dict["source"] == self.source
+        assert chat_boost_source_premium_dict["user"] == self.user.to_dict()
 
-        val = getattr(instance, param.name)
-        if hasattr(val, "to_dict"):  # convert the user object or any future ones to dict.
-            val = val.to_dict()
-        json_dict[param.name] = val
+    def test_equality(self, chat_boost_source_premium):
+        a = chat_boost_source_premium
+        b = ChatBoostSourcePremium(user=self.user)
+        c = Dice(5, "test")
 
-    return json_dict
+        assert a == b
+        assert hash(a) == hash(b)
 
+        assert a != c
+        assert hash(a) != hash(c)
 
-def iter_args(
-    instance: ChatBoostSource, de_json_inst: ChatBoostSource, 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.source, de_json_inst.source  # 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, dtm.datetime):  # Convert dtm to int
-            json_at = to_timestamp(json_at)
-        if (
-            param.default is not inspect.Parameter.empty and include_optional
-        ) or param.default is inspect.Parameter.empty:
-            yield inst_at, json_at
+@pytest.fixture(scope="module")
+def chat_boost_source_gift_code():
+    return ChatBoostSourceGiftCode(
+        user=TestChatBoostSourceGiftCodeWithoutRequest.user,
+    )
 
 
-@pytest.mark.parametrize(
-    "chat_boost_source",
-    [
-        chat_boost_source_gift_code,
-        chat_boost_source_giveaway,
-        chat_boost_source_premium,
-    ],
-    indirect=True,
-)
-class TestChatBoostSourceTypesWithoutRequest:
-    def test_slot_behaviour(self, chat_boost_source):
-        inst = chat_boost_source
+class TestChatBoostSourceGiftCodeWithoutRequest(ChatBoostDefaults):
+    source = ChatBoostSources.GIFT_CODE
+
+    def test_slot_behaviour(self, chat_boost_source_gift_code):
+        inst = chat_boost_source_gift_code
         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, offline_bot, chat_boost_source):
-        cls = chat_boost_source.__class__
-
-        json_dict = make_json_dict(chat_boost_source)
-        const_boost_source = ChatBoostSource.de_json(json_dict, offline_bot)
-        assert const_boost_source.api_kwargs == {}
-
-        assert isinstance(const_boost_source, ChatBoostSource)
-        assert isinstance(const_boost_source, cls)
-        for chat_mem_type_at, const_chat_mem_at in iter_args(
-            chat_boost_source, const_boost_source
-        ):
-            assert chat_mem_type_at == const_chat_mem_at
-
-    def test_de_json_all_args(self, offline_bot, chat_boost_source):
-        json_dict = make_json_dict(chat_boost_source, include_optional_args=True)
-        const_boost_source = ChatBoostSource.de_json(json_dict, offline_bot)
-        assert const_boost_source.api_kwargs == {}
-
-        assert isinstance(const_boost_source, ChatBoostSource)
-        assert isinstance(const_boost_source, chat_boost_source.__class__)
-        for c_mem_type_at, const_c_mem_at in iter_args(
-            chat_boost_source, const_boost_source, True
-        ):
-            assert c_mem_type_at == const_c_mem_at
-
-    def test_de_json_invalid_source(self, chat_boost_source, offline_bot):
-        json_dict = {"source": "invalid"}
-        chat_boost_source = ChatBoostSource.de_json(json_dict, offline_bot)
-
-        assert type(chat_boost_source) is ChatBoostSource
-        assert chat_boost_source.source == "invalid"
-
-    def test_de_json_subclass(self, chat_boost_source, offline_bot):
-        """This makes sure that e.g. ChatBoostSourcePremium(data, offline_bot) never returns a
-        ChatBoostSourceGiftCode instance."""
-        cls = chat_boost_source.__class__
-        json_dict = make_json_dict(chat_boost_source, True)
-        assert type(cls.de_json(json_dict, offline_bot)) is cls
+    def test_de_json(self, offline_bot):
+        json_dict = {
+            "user": self.user.to_dict(),
+        }
+        cbsgc = ChatBoostSourceGiftCode.de_json(json_dict, offline_bot)
 
-    def test_to_dict(self, chat_boost_source):
-        chat_boost_dict = chat_boost_source.to_dict()
+        assert cbsgc.api_kwargs == {}
+        assert cbsgc.user == self.user
 
-        assert isinstance(chat_boost_dict, dict)
-        assert chat_boost_dict["source"] == chat_boost_source.source
-        assert chat_boost_dict["user"] == chat_boost_source.user.to_dict()
+    def test_to_dict(self, chat_boost_source_gift_code):
+        chat_boost_source_gift_code_dict = chat_boost_source_gift_code.to_dict()
 
-        for slot in chat_boost_source.__slots__:  # additional verification for the optional args
-            if slot == "user":  # we already test "user" above:
-                continue
-            assert getattr(chat_boost_source, slot) == chat_boost_dict[slot]
+        assert isinstance(chat_boost_source_gift_code_dict, dict)
+        assert chat_boost_source_gift_code_dict["source"] == self.source
+        assert chat_boost_source_gift_code_dict["user"] == self.user.to_dict()
 
-    def test_equality(self, chat_boost_source):
-        a = ChatBoostSource(source="status")
-        b = ChatBoostSource(source="status")
-        c = chat_boost_source
-        d = deepcopy(chat_boost_source)
-        e = Dice(4, "emoji")
+    def test_equality(self, chat_boost_source_gift_code):
+        a = chat_boost_source_gift_code
+        b = ChatBoostSourceGiftCode(user=self.user)
+        c = Dice(5, "test")
 
         assert a == b
         assert hash(a) == hash(b)
@@ -238,23 +217,63 @@ def test_equality(self, chat_boost_source):
         assert a != c
         assert hash(a) != hash(c)
 
-        assert a != d
-        assert hash(a) != hash(d)
 
-        assert a != e
-        assert hash(a) != hash(e)
+@pytest.fixture(scope="module")
+def chat_boost_source_giveaway():
+    return ChatBoostSourceGiveaway(
+        user=TestChatBoostSourceGiveawayWithoutRequest.user,
+        giveaway_message_id=TestChatBoostSourceGiveawayWithoutRequest.giveaway_message_id,
+    )
+
 
-        assert c == d
-        assert hash(c) == hash(d)
+class TestChatBoostSourceGiveawayWithoutRequest(ChatBoostDefaults):
+    source = ChatBoostSources.GIVEAWAY
 
-        assert c != e
-        assert hash(c) != hash(e)
+    def test_slot_behaviour(self, chat_boost_source_giveaway):
+        inst = chat_boost_source_giveaway
+        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_enum_init(self, chat_boost_source):
-        cbs = ChatBoostSource(source="foo")
-        assert cbs.source == "foo"
-        cbs = ChatBoostSource(source="premium")
-        assert cbs.source == ChatBoostSources.PREMIUM
+    def test_de_json(self, offline_bot):
+        json_dict = {
+            "user": self.user.to_dict(),
+            "giveaway_message_id": self.giveaway_message_id,
+        }
+        cbsg = ChatBoostSourceGiveaway.de_json(json_dict, offline_bot)
+
+        assert cbsg.api_kwargs == {}
+        assert cbsg.user == self.user
+        assert cbsg.giveaway_message_id == self.giveaway_message_id
+
+    def test_to_dict(self, chat_boost_source_giveaway):
+        chat_boost_source_giveaway_dict = chat_boost_source_giveaway.to_dict()
+
+        assert isinstance(chat_boost_source_giveaway_dict, dict)
+        assert chat_boost_source_giveaway_dict["source"] == self.source
+        assert chat_boost_source_giveaway_dict["user"] == self.user.to_dict()
+        assert chat_boost_source_giveaway_dict["giveaway_message_id"] == self.giveaway_message_id
+
+    def test_equality(self, chat_boost_source_giveaway):
+        a = chat_boost_source_giveaway
+        b = ChatBoostSourceGiveaway(user=self.user, giveaway_message_id=self.giveaway_message_id)
+        c = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+
+@pytest.fixture(scope="module")
+def chat_boost():
+    return ChatBoost(
+        boost_id=ChatBoostDefaults.boost_id,
+        add_date=ChatBoostDefaults.date,
+        expiration_date=ChatBoostDefaults.date,
+        source=ChatBoostDefaults.default_source,
+    )
 
 
 class TestChatBoostWithoutRequest(ChatBoostDefaults):
@@ -266,30 +285,24 @@ def test_slot_behaviour(self, chat_boost):
 
     def test_de_json(self, offline_bot, chat_boost):
         json_dict = {
-            "boost_id": "2",
-            "add_date": self.date,
-            "expiration_date": self.date,
+            "boost_id": self.boost_id,
+            "add_date": to_timestamp(self.date),
+            "expiration_date": to_timestamp(self.date),
             "source": self.default_source.to_dict(),
         }
         cb = ChatBoost.de_json(json_dict, offline_bot)
 
-        assert isinstance(cb, ChatBoost)
-        assert isinstance(cb.add_date, dtm.datetime)
-        assert isinstance(cb.expiration_date, dtm.datetime)
-        assert isinstance(cb.source, ChatBoostSource)
-        with cb._unfrozen():
-            cb.add_date = to_timestamp(cb.add_date)
-            cb.expiration_date = to_timestamp(cb.expiration_date)
-
-        # We don't compare cbu.boost to self.boost because we have to update the _id_attrs (sigh)
-        for slot in cb.__slots__:
-            assert getattr(cb, slot) == getattr(chat_boost, slot), f"attribute {slot} differs"
+        assert cb.api_kwargs == {}
+        assert cb.boost_id == self.boost_id
+        assert (cb.add_date) == self.date
+        assert (cb.expiration_date) == self.date
+        assert cb.source == self.default_source
 
     def test_de_json_localization(self, offline_bot, raw_bot, tz_bot):
         json_dict = {
             "boost_id": "2",
-            "add_date": self.date,
-            "expiration_date": self.date,
+            "add_date": to_timestamp(self.date),
+            "expiration_date": to_timestamp(self.date),
             "source": self.default_source.to_dict(),
         }
 
@@ -310,8 +323,8 @@ def test_to_dict(self, chat_boost):
 
         assert isinstance(chat_boost_dict, dict)
         assert chat_boost_dict["boost_id"] == chat_boost.boost_id
-        assert chat_boost_dict["add_date"] == chat_boost.add_date
-        assert chat_boost_dict["expiration_date"] == chat_boost.expiration_date
+        assert chat_boost_dict["add_date"] == to_timestamp(chat_boost.add_date)
+        assert chat_boost_dict["expiration_date"] == to_timestamp(chat_boost.expiration_date)
         assert chat_boost_dict["source"] == chat_boost.source.to_dict()
 
     def test_equality(self):
@@ -341,6 +354,14 @@ def test_equality(self):
         assert hash(a) != hash(c)
 
 
+@pytest.fixture(scope="module")
+def chat_boost_updated(chat_boost):
+    return ChatBoostUpdated(
+        chat=ChatBoostDefaults.chat,
+        boost=chat_boost,
+    )
+
+
 class TestChatBoostUpdatedWithoutRequest(ChatBoostDefaults):
     def test_slot_behaviour(self, chat_boost_updated):
         inst = chat_boost_updated
@@ -351,25 +372,13 @@ def test_slot_behaviour(self, chat_boost_updated):
     def test_de_json(self, offline_bot, chat_boost):
         json_dict = {
             "chat": self.chat.to_dict(),
-            "boost": {
-                "boost_id": "2",
-                "add_date": self.date,
-                "expiration_date": self.date,
-                "source": self.default_source.to_dict(),
-            },
+            "boost": self.boost.to_dict(),
         }
         cbu = ChatBoostUpdated.de_json(json_dict, offline_bot)
 
-        assert isinstance(cbu, ChatBoostUpdated)
+        assert cbu.api_kwargs == {}
         assert cbu.chat == self.chat
-        # We don't compare cbu.boost to chat_boost because we have to update the _id_attrs (sigh)
-        with cbu.boost._unfrozen():
-            cbu.boost.add_date = to_timestamp(cbu.boost.add_date)
-            cbu.boost.expiration_date = to_timestamp(cbu.boost.expiration_date)
-        for slot in cbu.boost.__slots__:  # Assumes _id_attrs are same as slots
-            assert getattr(cbu.boost, slot) == getattr(chat_boost, slot), f"attr {slot} differs"
-
-    # no need to test localization since that is already tested in the above class.
+        assert cbu.boost == self.boost
 
     def test_to_dict(self, chat_boost_updated):
         chat_boost_updated_dict = chat_boost_updated.to_dict()
@@ -414,6 +423,16 @@ def test_equality(self):
         assert hash(a) != hash(c)
 
 
+@pytest.fixture(scope="module")
+def chat_boost_removed():
+    return ChatBoostRemoved(
+        chat=ChatBoostDefaults.chat,
+        boost_id=ChatBoostDefaults.boost_id,
+        remove_date=ChatBoostDefaults.date,
+        source=ChatBoostDefaults.default_source,
+    )
+
+
 class TestChatBoostRemovedWithoutRequest(ChatBoostDefaults):
     def test_slot_behaviour(self, chat_boost_removed):
         inst = chat_boost_removed
@@ -424,23 +443,23 @@ def test_slot_behaviour(self, chat_boost_removed):
     def test_de_json(self, offline_bot, chat_boost_removed):
         json_dict = {
             "chat": self.chat.to_dict(),
-            "boost_id": "2",
-            "remove_date": self.date,
+            "boost_id": self.boost_id,
+            "remove_date": to_timestamp(self.date),
             "source": self.default_source.to_dict(),
         }
         cbr = ChatBoostRemoved.de_json(json_dict, offline_bot)
 
-        assert isinstance(cbr, ChatBoostRemoved)
+        assert cbr.api_kwargs == {}
         assert cbr.chat == self.chat
         assert cbr.boost_id == self.boost_id
-        assert to_timestamp(cbr.remove_date) == self.date
+        assert cbr.remove_date == self.date
         assert cbr.source == self.default_source
 
     def test_de_json_localization(self, offline_bot, raw_bot, tz_bot):
         json_dict = {
             "chat": self.chat.to_dict(),
-            "boost_id": "2",
-            "remove_date": self.date,
+            "boost_id": self.boost_id,
+            "remove_date": to_timestamp(self.date),
             "source": self.default_source.to_dict(),
         }
 
@@ -462,7 +481,9 @@ def test_to_dict(self, chat_boost_removed):
         assert isinstance(chat_boost_removed_dict, dict)
         assert chat_boost_removed_dict["chat"] == chat_boost_removed.chat.to_dict()
         assert chat_boost_removed_dict["boost_id"] == chat_boost_removed.boost_id
-        assert chat_boost_removed_dict["remove_date"] == chat_boost_removed.remove_date
+        assert chat_boost_removed_dict["remove_date"] == to_timestamp(
+            chat_boost_removed.remove_date
+        )
         assert chat_boost_removed_dict["source"] == chat_boost_removed.source.to_dict()
 
     def test_equality(self):
@@ -492,6 +513,13 @@ def test_equality(self):
         assert hash(a) != hash(c)
 
 
+@pytest.fixture(scope="module")
+def user_chat_boosts(chat_boost):
+    return UserChatBoosts(
+        boosts=[chat_boost],
+    )
+
+
 class TestUserChatBoostsWithoutRequest(ChatBoostDefaults):
     def test_slot_behaviour(self, user_chat_boosts):
         inst = user_chat_boosts
@@ -502,22 +530,13 @@ def test_slot_behaviour(self, user_chat_boosts):
     def test_de_json(self, offline_bot, user_chat_boosts):
         json_dict = {
             "boosts": [
-                {
-                    "boost_id": "2",
-                    "add_date": self.date,
-                    "expiration_date": self.date,
-                    "source": self.default_source.to_dict(),
-                }
+                self.boost.to_dict(),
             ]
         }
         ucb = UserChatBoosts.de_json(json_dict, offline_bot)
 
-        assert isinstance(ucb, UserChatBoosts)
-        assert isinstance(ucb.boosts[0], ChatBoost)
-        assert ucb.boosts[0].boost_id == self.boost_id
-        assert to_timestamp(ucb.boosts[0].add_date) == self.date
-        assert to_timestamp(ucb.boosts[0].expiration_date) == self.date
-        assert ucb.boosts[0].source == self.default_source
+        assert ucb.api_kwargs == {}
+        assert ucb.boosts[0] == self.boost
 
     def test_to_dict(self, user_chat_boosts):
         user_chat_boosts_dict = user_chat_boosts.to_dict()
diff --git a/tests/test_chatmember.py b/tests/test_chatmember.py
index 359e0727878..fdf6136f701 100644
--- a/tests/test_chatmember.py
+++ b/tests/test_chatmember.py
@@ -34,257 +34,443 @@
     User,
 )
 from telegram._utils.datetime import UTC, to_timestamp
+from telegram.constants import ChatMemberStatus
 from tests.auxil.slots import mro_slots
 
-ignored = ["self", "api_kwargs"]
-
-
-class CMDefaults:
-    user = User(1, "First name", False)
-    custom_title: str = "PTB"
-    is_anonymous: bool = True
-    until_date: dtm.datetime = to_timestamp(dtm.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_video_chats: bool = True
-    can_manage_topics: bool = True
-    can_send_audios: bool = True
-    can_send_documents: bool = True
-    can_send_photos: bool = True
-    can_send_videos: bool = True
-    can_send_video_notes: bool = True
-    can_send_voice_notes: bool = True
-    can_post_stories: bool = True
-    can_edit_stories: bool = True
-    can_delete_stories: bool = True
 
+@pytest.fixture
+def chat_member():
+    return ChatMember(ChatMemberTestBase.user, ChatMemberTestBase.status)
+
+
+class ChatMemberTestBase:
+    status = ChatMemberStatus.MEMBER
+    user = User(1, "test_user", is_bot=False)
+    is_anonymous = True
+    custom_title = "test_title"
+    can_be_edited = True
+    can_manage_chat = True
+    can_delete_messages = True
+    can_manage_video_chats = True
+    can_restrict_members = True
+    can_promote_members = True
+    can_change_info = True
+    can_invite_users = True
+    can_post_messages = True
+    can_edit_messages = True
+    can_pin_messages = True
+    can_post_stories = True
+    can_edit_stories = True
+    can_delete_stories = True
+    can_manage_topics = True
+    until_date = dtm.datetime.now(UTC).replace(microsecond=0)
+    can_send_polls = True
+    can_send_other_messages = True
+    can_add_web_page_previews = True
+    can_send_audios = True
+    can_send_documents = True
+    can_send_photos = True
+    can_send_videos = True
+    can_send_video_notes = True
+    can_send_voice_notes = True
+    can_send_messages = True
+    is_member = True
+
+
+class TestChatMemberWithoutRequest(ChatMemberTestBase):
+    def test_slot_behaviour(self, chat_member):
+        inst = chat_member
+        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 chat_member_owner():
-    return ChatMemberOwner(CMDefaults.user, CMDefaults.is_anonymous, CMDefaults.custom_title)
+    def test_status_enum_conversion(self, chat_member):
+        assert type(ChatMember(ChatMemberTestBase.user, "member").status) is ChatMemberStatus
+        assert ChatMember(ChatMemberTestBase.user, "unknown").status == "unknown"
+
+    def test_de_json(self, offline_bot):
+        data = {"status": "unknown", "user": self.user.to_dict()}
+        chat_member = ChatMember.de_json(data, offline_bot)
+        assert chat_member.api_kwargs == {}
+        assert chat_member.status == "unknown"
+        assert chat_member.user == self.user
+
+    @pytest.mark.parametrize(
+        ("status", "subclass"),
+        [
+            ("administrator", ChatMemberAdministrator),
+            ("kicked", ChatMemberBanned),
+            ("left", ChatMemberLeft),
+            ("member", ChatMemberMember),
+            ("creator", ChatMemberOwner),
+            ("restricted", ChatMemberRestricted),
+        ],
+    )
+    def test_de_json_subclass(self, offline_bot, status, subclass):
+        json_dict = {
+            "status": status,
+            "user": self.user.to_dict(),
+            "is_anonymous": self.is_anonymous,
+            "is_member": self.is_member,
+            "until_date": to_timestamp(self.until_date),
+            **{name: value for name, value in inspect.getmembers(self) if name.startswith("can_")},
+        }
+        chat_member = ChatMember.de_json(json_dict, offline_bot)
+
+        assert type(chat_member) is subclass
+        assert set(chat_member.api_kwargs.keys()) == set(json_dict.keys()) - set(
+            subclass.__slots__
+        ) - {"status", "user"}
+        assert chat_member.user == self.user
+
+    def test_to_dict(self, chat_member):
+        assert chat_member.to_dict() == {
+            "status": chat_member.status,
+            "user": chat_member.user.to_dict(),
+        }
 
+    def test_equality(self, chat_member):
+        a = chat_member
+        b = ChatMember(self.user, self.status)
+        c = ChatMember(self.user, "unknown")
+        d = ChatMember(User(2, "test_bot", is_bot=True), self.status)
+        e = Dice(5, "test")
 
+        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)
+
+
+@pytest.fixture
 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_video_chats,
-        CMDefaults.can_restrict_members,
-        CMDefaults.can_promote_members,
-        CMDefaults.can_change_info,
-        CMDefaults.can_invite_users,
-        CMDefaults.can_post_stories,
-        CMDefaults.can_edit_stories,
-        CMDefaults.can_delete_stories,
-        CMDefaults.can_post_messages,
-        CMDefaults.can_edit_messages,
-        CMDefaults.can_pin_messages,
-        CMDefaults.can_manage_topics,
-        CMDefaults.custom_title,
+        TestChatMemberAdministratorWithoutRequest.user,
+        TestChatMemberAdministratorWithoutRequest.can_be_edited,
+        TestChatMemberAdministratorWithoutRequest.can_change_info,
+        TestChatMemberAdministratorWithoutRequest.can_delete_messages,
+        TestChatMemberAdministratorWithoutRequest.can_delete_stories,
+        TestChatMemberAdministratorWithoutRequest.can_edit_messages,
+        TestChatMemberAdministratorWithoutRequest.can_edit_stories,
+        TestChatMemberAdministratorWithoutRequest.can_invite_users,
+        TestChatMemberAdministratorWithoutRequest.can_manage_chat,
+        TestChatMemberAdministratorWithoutRequest.can_manage_topics,
+        TestChatMemberAdministratorWithoutRequest.can_manage_video_chats,
+        TestChatMemberAdministratorWithoutRequest.can_pin_messages,
+        TestChatMemberAdministratorWithoutRequest.can_post_messages,
+        TestChatMemberAdministratorWithoutRequest.can_post_stories,
+        TestChatMemberAdministratorWithoutRequest.can_promote_members,
+        TestChatMemberAdministratorWithoutRequest.can_restrict_members,
+        TestChatMemberAdministratorWithoutRequest.custom_title,
+        TestChatMemberAdministratorWithoutRequest.is_anonymous,
     )
 
 
-def chat_member_member():
-    return ChatMemberMember(CMDefaults.user, until_date=CMDefaults.until_date)
+class TestChatMemberAdministratorWithoutRequest(ChatMemberTestBase):
+    status = ChatMemberStatus.ADMINISTRATOR
 
+    def test_slot_behaviour(self, chat_member_administrator):
+        inst = chat_member_administrator
+        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 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_polls,
-        CMDefaults.can_send_other_messages,
-        CMDefaults.can_add_web_page_previews,
-        CMDefaults.can_manage_topics,
-        CMDefaults.until_date,
-        CMDefaults.can_send_audios,
-        CMDefaults.can_send_documents,
-        CMDefaults.can_send_photos,
-        CMDefaults.can_send_videos,
-        CMDefaults.can_send_video_notes,
-        CMDefaults.can_send_voice_notes,
+    def test_de_json(self, offline_bot):
+        data = {
+            "user": self.user.to_dict(),
+            "can_be_edited": self.can_be_edited,
+            "can_change_info": self.can_change_info,
+            "can_delete_messages": self.can_delete_messages,
+            "can_delete_stories": self.can_delete_stories,
+            "can_edit_messages": self.can_edit_messages,
+            "can_edit_stories": self.can_edit_stories,
+            "can_invite_users": self.can_invite_users,
+            "can_manage_chat": self.can_manage_chat,
+            "can_manage_topics": self.can_manage_topics,
+            "can_manage_video_chats": self.can_manage_video_chats,
+            "can_pin_messages": self.can_pin_messages,
+            "can_post_messages": self.can_post_messages,
+            "can_post_stories": self.can_post_stories,
+            "can_promote_members": self.can_promote_members,
+            "can_restrict_members": self.can_restrict_members,
+            "custom_title": self.custom_title,
+            "is_anonymous": self.is_anonymous,
+        }
+        chat_member = ChatMemberAdministrator.de_json(data, offline_bot)
+
+        assert type(chat_member) is ChatMemberAdministrator
+        assert chat_member.api_kwargs == {}
+
+        assert chat_member.user == self.user
+        assert chat_member.can_be_edited == self.can_be_edited
+        assert chat_member.can_change_info == self.can_change_info
+        assert chat_member.can_delete_messages == self.can_delete_messages
+        assert chat_member.can_delete_stories == self.can_delete_stories
+        assert chat_member.can_edit_messages == self.can_edit_messages
+        assert chat_member.can_edit_stories == self.can_edit_stories
+        assert chat_member.can_invite_users == self.can_invite_users
+        assert chat_member.can_manage_chat == self.can_manage_chat
+        assert chat_member.can_manage_topics == self.can_manage_topics
+        assert chat_member.can_manage_video_chats == self.can_manage_video_chats
+        assert chat_member.can_pin_messages == self.can_pin_messages
+        assert chat_member.can_post_messages == self.can_post_messages
+        assert chat_member.can_post_stories == self.can_post_stories
+        assert chat_member.can_promote_members == self.can_promote_members
+        assert chat_member.can_restrict_members == self.can_restrict_members
+        assert chat_member.custom_title == self.custom_title
+        assert chat_member.is_anonymous == self.is_anonymous
+
+    def test_to_dict(self, chat_member_administrator):
+        assert chat_member_administrator.to_dict() == {
+            "status": chat_member_administrator.status,
+            "user": chat_member_administrator.user.to_dict(),
+            "can_be_edited": chat_member_administrator.can_be_edited,
+            "can_change_info": chat_member_administrator.can_change_info,
+            "can_delete_messages": chat_member_administrator.can_delete_messages,
+            "can_delete_stories": chat_member_administrator.can_delete_stories,
+            "can_edit_messages": chat_member_administrator.can_edit_messages,
+            "can_edit_stories": chat_member_administrator.can_edit_stories,
+            "can_invite_users": chat_member_administrator.can_invite_users,
+            "can_manage_chat": chat_member_administrator.can_manage_chat,
+            "can_manage_topics": chat_member_administrator.can_manage_topics,
+            "can_manage_video_chats": chat_member_administrator.can_manage_video_chats,
+            "can_pin_messages": chat_member_administrator.can_pin_messages,
+            "can_post_messages": chat_member_administrator.can_post_messages,
+            "can_post_stories": chat_member_administrator.can_post_stories,
+            "can_promote_members": chat_member_administrator.can_promote_members,
+            "can_restrict_members": chat_member_administrator.can_restrict_members,
+            "custom_title": chat_member_administrator.custom_title,
+            "is_anonymous": chat_member_administrator.is_anonymous,
+        }
+
+    def test_equality(self, chat_member_administrator):
+        a = chat_member_administrator
+        b = ChatMemberAdministrator(
+            User(1, "test_user", is_bot=False),
+            True,
+            True,
+            True,
+            True,
+            True,
+            True,
+            True,
+            True,
+            True,
+            True,
+            True,
+            True,
+        )
+        c = ChatMemberAdministrator(
+            User(1, "test_user", is_bot=False),
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+        )
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a == c
+        assert hash(a) == hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def chat_member_banned():
+    return ChatMemberBanned(
+        TestChatMemberBannedWithoutRequest.user,
+        TestChatMemberBannedWithoutRequest.until_date,
     )
 
 
+class TestChatMemberBannedWithoutRequest(ChatMemberTestBase):
+    status = ChatMemberStatus.BANNED
+
+    def test_slot_behaviour(self, chat_member_banned):
+        inst = chat_member_banned
+        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(self, offline_bot):
+        data = {
+            "user": self.user.to_dict(),
+            "until_date": to_timestamp(self.until_date),
+        }
+        chat_member = ChatMemberBanned.de_json(data, offline_bot)
+
+        assert type(chat_member) is ChatMemberBanned
+        assert chat_member.api_kwargs == {}
+
+        assert chat_member.user == self.user
+        assert chat_member.until_date == self.until_date
+
+    def test_de_json_localization(self, tz_bot, offline_bot, raw_bot):
+        json_dict = {
+            "user": self.user.to_dict(),
+            "until_date": to_timestamp(self.until_date),
+        }
+
+        cmb_raw = ChatMemberBanned.de_json(json_dict, raw_bot)
+        cmb_bot = ChatMemberBanned.de_json(json_dict, offline_bot)
+        cmb_bot_tz = ChatMemberBanned.de_json(json_dict, tz_bot)
+
+        # comparing utcoffsets because comparing timezones is unpredicatable
+        cmb_bot_tz_offset = cmb_bot_tz.until_date.utcoffset()
+        tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
+            cmb_bot_tz.until_date.replace(tzinfo=None)
+        )
+
+        assert cmb_raw.until_date.tzinfo == UTC
+        assert cmb_bot.until_date.tzinfo == UTC
+        assert cmb_bot_tz_offset == tz_bot_offset
+
+    def test_to_dict(self, chat_member_banned):
+        assert chat_member_banned.to_dict() == {
+            "status": chat_member_banned.status,
+            "user": chat_member_banned.user.to_dict(),
+            "until_date": to_timestamp(chat_member_banned.until_date),
+        }
+
+    def test_equality(self, chat_member_banned):
+        a = chat_member_banned
+        b = ChatMemberBanned(
+            User(1, "test_user", is_bot=False), dtm.datetime.now(UTC).replace(microsecond=0)
+        )
+        c = ChatMemberBanned(
+            User(2, "test_bot", is_bot=True), dtm.datetime.now(UTC).replace(microsecond=0)
+        )
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
 def chat_member_left():
-    return ChatMemberLeft(CMDefaults.user)
+    return ChatMemberLeft(TestChatMemberLeftWithoutRequest.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)
-        # or if the param is optional but for backwards compatability
-        elif (
-            param.default is not inspect.Parameter.empty and include_optional_args
-        ) or param.name in ["can_delete_stories", "can_post_stories", "can_edit_stories"]:
-            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, dtm.datetime):  # Convert dtm to int
-            json_at = to_timestamp(json_at)
-        if (
-            param.default is not inspect.Parameter.empty and include_optional
-        ) or param.default is inspect.Parameter.empty:
-            yield inst_at, json_at
+class TestChatMemberLeftWithoutRequest(ChatMemberTestBase):
+    status = ChatMemberStatus.LEFT
+
+    def test_slot_behaviour(self, chat_member_left):
+        inst = chat_member_left
+        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(self, offline_bot):
+        data = {"user": self.user.to_dict()}
+        chat_member = ChatMemberLeft.de_json(data, offline_bot)
+
+        assert type(chat_member) is ChatMemberLeft
+        assert chat_member.api_kwargs == {}
+
+        assert chat_member.user == self.user
+
+    def test_to_dict(self, chat_member_left):
+        assert chat_member_left.to_dict() == {
+            "status": chat_member_left.status,
+            "user": chat_member_left.user.to_dict(),
+        }
+
+    def test_equality(self, chat_member_left):
+        a = chat_member_left
+        b = ChatMemberLeft(User(1, "test_user", is_bot=False))
+        c = ChatMemberLeft(User(2, "test_bot", is_bot=True))
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
 
 
 @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,
-)
-class TestChatMemberTypesWithoutRequest:
-    def test_slot_behaviour(self, chat_member_type):
-        inst = chat_member_type
+def chat_member_member():
+    return ChatMemberMember(TestChatMemberMemberWithoutRequest.user)
+
+
+class TestChatMemberMemberWithoutRequest(ChatMemberTestBase):
+    status = ChatMemberStatus.MEMBER
+
+    def test_slot_behaviour(self, chat_member_member):
+        inst = chat_member_member
         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, offline_bot, chat_member_type):
-        cls = chat_member_type.__class__
-
-        json_dict = make_json_dict(chat_member_type)
-        const_chat_member = ChatMember.de_json(json_dict, offline_bot)
-        assert const_chat_member.api_kwargs == {}
-
-        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
-
-    def test_de_json_all_args(self, offline_bot, chat_member_type):
-        json_dict = make_json_dict(chat_member_type, include_optional_args=True)
-        const_chat_member = ChatMember.de_json(json_dict, offline_bot)
-        assert const_chat_member.api_kwargs == {}
-
-        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
-
-    def test_de_json_chatmemberbanned_localization(
-        self, chat_member_type, tz_bot, offline_bot, raw_bot
-    ):
-        # We only test two classes because the other three don't have datetimes in them.
-        if isinstance(
-            chat_member_type, (ChatMemberBanned, ChatMemberRestricted, ChatMemberMember)
-        ):
-            json_dict = make_json_dict(chat_member_type, include_optional_args=True)
-            chatmember_raw = ChatMember.de_json(json_dict, raw_bot)
-            chatmember_bot = ChatMember.de_json(json_dict, offline_bot)
-            chatmember_tz = ChatMember.de_json(json_dict, tz_bot)
-
-            # comparing utcoffsets because comparing timezones is unpredicatable
-            chatmember_offset = chatmember_tz.until_date.utcoffset()
-            tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
-                chatmember_tz.until_date.replace(tzinfo=None)
-            )
-
-            assert chatmember_raw.until_date.tzinfo == UTC
-            assert chatmember_bot.until_date.tzinfo == UTC
-            assert chatmember_offset == tz_bot_offset
-
-    def test_de_json_invalid_status(self, chat_member_type, offline_bot):
-        json_dict = {"status": "invalid", "user": CMDefaults.user.to_dict()}
-        chat_member_type = ChatMember.de_json(json_dict, offline_bot)
-
-        assert type(chat_member_type) is ChatMember
-        assert chat_member_type.status == "invalid"
-
-    def test_de_json_subclass(self, chat_member_type, offline_bot, chat_id):
-        """This makes sure that e.g. ChatMemberAdministrator(data, offline_bot) never returns a
-        ChatMemberBanned instance."""
-        cls = chat_member_type.__class__
-        json_dict = make_json_dict(chat_member_type, True)
-        assert type(cls.de_json(json_dict, offline_bot)) is cls
-
-    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_type.status
-        assert chat_member_dict["user"] == chat_member_type.user.to_dict()
-
-        for slot in chat_member_type.__slots__:  # additional verification for the optional args
-            assert getattr(chat_member_type, slot) == chat_member_dict[slot]
-
-    def test_chat_member_restricted_api_kwargs(self, chat_member_type):
-        json_dict = make_json_dict(chat_member_restricted())
-        json_dict["can_send_media_messages"] = "can_send_media_messages"
-        chat_member_restricted_instance = ChatMember.de_json(json_dict, None)
-        assert chat_member_restricted_instance.api_kwargs == {
-            "can_send_media_messages": "can_send_media_messages",
+    def test_de_json(self, offline_bot):
+        data = {"user": self.user.to_dict(), "until_date": to_timestamp(self.until_date)}
+        chat_member = ChatMemberMember.de_json(data, offline_bot)
+
+        assert type(chat_member) is ChatMemberMember
+        assert chat_member.api_kwargs == {}
+
+        assert chat_member.user == self.user
+        assert chat_member.until_date == self.until_date
+
+    def test_de_json_localization(self, tz_bot, offline_bot, raw_bot):
+        json_dict = {
+            "user": self.user.to_dict(),
+            "until_date": to_timestamp(self.until_date),
         }
 
-    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")
+        cmm_raw = ChatMemberMember.de_json(json_dict, raw_bot)
+        cmm_bot = ChatMemberMember.de_json(json_dict, offline_bot)
+        cmm_bot_tz = ChatMemberMember.de_json(json_dict, tz_bot)
+
+        # comparing utcoffsets because comparing timezones is unpredicatable
+        cmm_bot_tz_offset = cmm_bot_tz.until_date.utcoffset()
+        tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
+            cmm_bot_tz.until_date.replace(tzinfo=None)
+        )
+
+        assert cmm_raw.until_date.tzinfo == UTC
+        assert cmm_bot.until_date.tzinfo == UTC
+        assert cmm_bot_tz_offset == tz_bot_offset
+
+    def test_to_dict(self, chat_member_member):
+        assert chat_member_member.to_dict() == {
+            "status": chat_member_member.status,
+            "user": chat_member_member.user.to_dict(),
+        }
+
+    def test_equality(self, chat_member_member):
+        a = chat_member_member
+        b = ChatMemberMember(User(1, "test_user", is_bot=False))
+        c = ChatMemberMember(User(2, "test_bot", is_bot=True))
+        d = Dice(5, "test")
 
         assert a == b
         assert hash(a) == hash(b)
@@ -295,11 +481,209 @@ def test_equality(self, chat_member_type):
         assert a != d
         assert hash(a) != hash(d)
 
-        assert a != e
-        assert hash(a) != hash(e)
 
-        assert c == d
-        assert hash(c) == hash(d)
+@pytest.fixture
+def chat_member_owner():
+    return ChatMemberOwner(
+        TestChatMemberOwnerWithoutRequest.user,
+        TestChatMemberOwnerWithoutRequest.is_anonymous,
+        TestChatMemberOwnerWithoutRequest.custom_title,
+    )
+
+
+class TestChatMemberOwnerWithoutRequest(ChatMemberTestBase):
+    status = ChatMemberStatus.OWNER
+
+    def test_slot_behaviour(self, chat_member_owner):
+        inst = chat_member_owner
+        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"
 
-        assert c != e
-        assert hash(c) != hash(e)
+    def test_de_json(self, offline_bot):
+        data = {
+            "user": self.user.to_dict(),
+            "is_anonymous": self.is_anonymous,
+            "custom_title": self.custom_title,
+        }
+        chat_member = ChatMemberOwner.de_json(data, offline_bot)
+
+        assert type(chat_member) is ChatMemberOwner
+        assert chat_member.api_kwargs == {}
+
+        assert chat_member.user == self.user
+        assert chat_member.is_anonymous == self.is_anonymous
+        assert chat_member.custom_title == self.custom_title
+
+    def test_to_dict(self, chat_member_owner):
+        assert chat_member_owner.to_dict() == {
+            "status": chat_member_owner.status,
+            "user": chat_member_owner.user.to_dict(),
+            "is_anonymous": chat_member_owner.is_anonymous,
+            "custom_title": chat_member_owner.custom_title,
+        }
+
+    def test_equality(self, chat_member_owner):
+        a = chat_member_owner
+        b = ChatMemberOwner(User(1, "test_user", is_bot=False), True, "test_title")
+        c = ChatMemberOwner(User(1, "test_user", is_bot=False), False, "test_title")
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a == c
+        assert hash(a) == hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def chat_member_restricted():
+    return ChatMemberRestricted(
+        user=TestChatMemberRestrictedWithoutRequest.user,
+        can_add_web_page_previews=TestChatMemberRestrictedWithoutRequest.can_add_web_page_previews,
+        can_change_info=TestChatMemberRestrictedWithoutRequest.can_change_info,
+        can_invite_users=TestChatMemberRestrictedWithoutRequest.can_invite_users,
+        can_manage_topics=TestChatMemberRestrictedWithoutRequest.can_manage_topics,
+        can_pin_messages=TestChatMemberRestrictedWithoutRequest.can_pin_messages,
+        can_send_audios=TestChatMemberRestrictedWithoutRequest.can_send_audios,
+        can_send_documents=TestChatMemberRestrictedWithoutRequest.can_send_documents,
+        can_send_messages=TestChatMemberRestrictedWithoutRequest.can_send_messages,
+        can_send_other_messages=TestChatMemberRestrictedWithoutRequest.can_send_other_messages,
+        can_send_photos=TestChatMemberRestrictedWithoutRequest.can_send_photos,
+        can_send_polls=TestChatMemberRestrictedWithoutRequest.can_send_polls,
+        can_send_video_notes=TestChatMemberRestrictedWithoutRequest.can_send_video_notes,
+        can_send_videos=TestChatMemberRestrictedWithoutRequest.can_send_videos,
+        can_send_voice_notes=TestChatMemberRestrictedWithoutRequest.can_send_voice_notes,
+        is_member=TestChatMemberRestrictedWithoutRequest.is_member,
+        until_date=TestChatMemberRestrictedWithoutRequest.until_date,
+    )
+
+
+class TestChatMemberRestrictedWithoutRequest(ChatMemberTestBase):
+    status = ChatMemberStatus.RESTRICTED
+
+    def test_slot_behaviour(self, chat_member_restricted):
+        inst = chat_member_restricted
+        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(self, offline_bot):
+        data = {
+            "user": self.user.to_dict(),
+            "can_add_web_page_previews": self.can_add_web_page_previews,
+            "can_change_info": self.can_change_info,
+            "can_invite_users": self.can_invite_users,
+            "can_manage_topics": self.can_manage_topics,
+            "can_pin_messages": self.can_pin_messages,
+            "can_send_audios": self.can_send_audios,
+            "can_send_documents": self.can_send_documents,
+            "can_send_messages": self.can_send_messages,
+            "can_send_other_messages": self.can_send_other_messages,
+            "can_send_photos": self.can_send_photos,
+            "can_send_polls": self.can_send_polls,
+            "can_send_video_notes": self.can_send_video_notes,
+            "can_send_videos": self.can_send_videos,
+            "can_send_voice_notes": self.can_send_voice_notes,
+            "is_member": self.is_member,
+            "until_date": to_timestamp(self.until_date),
+            # legacy argument
+            "can_send_media_messages": False,
+        }
+        chat_member = ChatMemberRestricted.de_json(data, offline_bot)
+
+        assert type(chat_member) is ChatMemberRestricted
+        assert chat_member.api_kwargs == {"can_send_media_messages": False}
+
+        assert chat_member.user == self.user
+        assert chat_member.can_add_web_page_previews == self.can_add_web_page_previews
+        assert chat_member.can_change_info == self.can_change_info
+        assert chat_member.can_invite_users == self.can_invite_users
+        assert chat_member.can_manage_topics == self.can_manage_topics
+        assert chat_member.can_pin_messages == self.can_pin_messages
+        assert chat_member.can_send_audios == self.can_send_audios
+        assert chat_member.can_send_documents == self.can_send_documents
+        assert chat_member.can_send_messages == self.can_send_messages
+        assert chat_member.can_send_other_messages == self.can_send_other_messages
+        assert chat_member.can_send_photos == self.can_send_photos
+        assert chat_member.can_send_polls == self.can_send_polls
+        assert chat_member.can_send_video_notes == self.can_send_video_notes
+        assert chat_member.can_send_videos == self.can_send_videos
+        assert chat_member.can_send_voice_notes == self.can_send_voice_notes
+        assert chat_member.is_member == self.is_member
+        assert chat_member.until_date == self.until_date
+
+    def test_de_json_localization(self, tz_bot, offline_bot, raw_bot, chat_member_restricted):
+        json_dict = chat_member_restricted.to_dict()
+
+        cmr_raw = ChatMemberRestricted.de_json(json_dict, raw_bot)
+        cmr_bot = ChatMemberRestricted.de_json(json_dict, offline_bot)
+        cmr_bot_tz = ChatMemberRestricted.de_json(json_dict, tz_bot)
+
+        # comparing utcoffsets because comparing timezones is unpredicatable
+        cmr_bot_tz_offset = cmr_bot_tz.until_date.utcoffset()
+        tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
+            cmr_bot_tz.until_date.replace(tzinfo=None)
+        )
+
+        assert cmr_raw.until_date.tzinfo == UTC
+        assert cmr_bot.until_date.tzinfo == UTC
+        assert cmr_bot_tz_offset == tz_bot_offset
+
+    def test_to_dict(self, chat_member_restricted):
+        assert chat_member_restricted.to_dict() == {
+            "status": chat_member_restricted.status,
+            "user": chat_member_restricted.user.to_dict(),
+            "can_add_web_page_previews": chat_member_restricted.can_add_web_page_previews,
+            "can_change_info": chat_member_restricted.can_change_info,
+            "can_invite_users": chat_member_restricted.can_invite_users,
+            "can_manage_topics": chat_member_restricted.can_manage_topics,
+            "can_pin_messages": chat_member_restricted.can_pin_messages,
+            "can_send_audios": chat_member_restricted.can_send_audios,
+            "can_send_documents": chat_member_restricted.can_send_documents,
+            "can_send_messages": chat_member_restricted.can_send_messages,
+            "can_send_other_messages": chat_member_restricted.can_send_other_messages,
+            "can_send_photos": chat_member_restricted.can_send_photos,
+            "can_send_polls": chat_member_restricted.can_send_polls,
+            "can_send_video_notes": chat_member_restricted.can_send_video_notes,
+            "can_send_videos": chat_member_restricted.can_send_videos,
+            "can_send_voice_notes": chat_member_restricted.can_send_voice_notes,
+            "is_member": chat_member_restricted.is_member,
+            "until_date": to_timestamp(chat_member_restricted.until_date),
+        }
+
+    def test_equality(self, chat_member_restricted):
+        a = chat_member_restricted
+        b = deepcopy(chat_member_restricted)
+        c = ChatMemberRestricted(
+            User(1, "test_user", is_bot=False),
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+            self.until_date,
+            False,
+            False,
+            False,
+            False,
+            False,
+            False,
+        )
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a == c
+        assert hash(a) == hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
diff --git a/tests/test_menubutton.py b/tests/test_menubutton.py
index ac03f309671..5b63c197a70 100644
--- a/tests/test_menubutton.py
+++ b/tests/test_menubutton.py
@@ -16,8 +16,6 @@
 #
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
-from copy import deepcopy
-
 import pytest
 
 from telegram import (
@@ -32,134 +30,102 @@
 from tests.auxil.slots import mro_slots
 
 
-@pytest.fixture(
-    scope="module",
-    params=[
-        MenuButton.DEFAULT,
-        MenuButton.WEB_APP,
-        MenuButton.COMMANDS,
-    ],
-)
-def scope_type(request):
-    return request.param
-
-
-@pytest.fixture(
-    scope="module",
-    params=[
-        MenuButtonDefault,
-        MenuButtonCommands,
-        MenuButtonWebApp,
-    ],
-    ids=[
-        MenuButton.DEFAULT,
-        MenuButton.COMMANDS,
-        MenuButton.WEB_APP,
-    ],
-)
-def scope_class(request):
-    return request.param
-
-
-@pytest.fixture(
-    scope="module",
-    params=[
-        (MenuButtonDefault, MenuButton.DEFAULT),
-        (MenuButtonCommands, MenuButton.COMMANDS),
-        (MenuButtonWebApp, MenuButton.WEB_APP),
-    ],
-    ids=[
-        MenuButton.DEFAULT,
-        MenuButton.COMMANDS,
-        MenuButton.WEB_APP,
-    ],
-)
-def scope_class_and_type(request):
-    return request.param
-
-
-@pytest.fixture(scope="module")
-def menu_button(scope_class_and_type):
-    # We use de_json here so that we don't have to worry about which class gets which arguments
-    return scope_class_and_type[0].de_json(
-        {
-            "type": scope_class_and_type[1],
-            "text": MenuButtonTestBase.text,
-            "web_app": MenuButtonTestBase.web_app.to_dict(),
-        },
-        bot=None,
-    )
+@pytest.fixture
+def menu_button():
+    return MenuButton(MenuButtonTestBase.type)
 
 
 class MenuButtonTestBase:
-    text = "button_text"
-    web_app = WebAppInfo(url="https://python-telegram-bot.org/web_app")
+    type = MenuButtonType.DEFAULT
+    text = "this is a test string"
+    web_app = WebAppInfo(url="https://python-telegram-bot.org")
 
 
-# All the scope types are very similar, so we test everything via parametrization
 class TestMenuButtonWithoutRequest(MenuButtonTestBase):
     def test_slot_behaviour(self, menu_button):
-        for attr in menu_button.__slots__:
-            assert getattr(menu_button, attr, "err") != "err", f"got extra slot '{attr}'"
-        assert len(mro_slots(menu_button)) == len(set(mro_slots(menu_button))), "duplicate slot"
-
-    def test_de_json(self, offline_bot, scope_class_and_type):
-        cls = scope_class_and_type[0]
-        type_ = scope_class_and_type[1]
-
-        json_dict = {"type": type_, "text": self.text, "web_app": self.web_app.to_dict()}
-        menu_button = MenuButton.de_json(json_dict, offline_bot)
-        assert set(menu_button.api_kwargs.keys()) == {"text", "web_app"} - set(cls.__slots__)
-
-        assert isinstance(menu_button, MenuButton)
-        assert type(menu_button) is cls
-        assert menu_button.type == type_
-        if "web_app" in cls.__slots__:
-            assert menu_button.web_app == self.web_app
-        if "text" in cls.__slots__:
-            assert menu_button.text == self.text
-
-    def test_de_json_invalid_type(self, offline_bot):
-        json_dict = {"type": "invalid", "text": self.text, "web_app": self.web_app.to_dict()}
-        menu_button = MenuButton.de_json(json_dict, offline_bot)
-        assert menu_button.api_kwargs == {"text": self.text, "web_app": self.web_app.to_dict()}
-
-        assert type(menu_button) is MenuButton
-        assert menu_button.type == "invalid"
-
-    def test_de_json_subclass(self, scope_class, offline_bot):
-        """This makes sure that e.g. MenuButtonDefault(data) never returns a
-        MenuButtonChat instance."""
-        json_dict = {"type": "invalid", "text": self.text, "web_app": self.web_app.to_dict()}
-        assert type(scope_class.de_json(json_dict, offline_bot)) is scope_class
-
-    def test_de_json_empty_data(self, scope_class):
-        if scope_class in (MenuButtonWebApp,):
-            pytest.skip(
-                "This test is not relevant for subclasses that have more attributes than just type"
-            )
-        assert isinstance(scope_class.de_json({}, None), scope_class)
+        inst = menu_button
+        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_type_enum_conversion(self, menu_button):
+        assert type(MenuButton("default").type) is MenuButtonType
+        assert MenuButton("unknown").type == "unknown"
+
+    def test_de_json(self, offline_bot):
+        data = {"type": "unknown"}
+        transaction_partner = MenuButton.de_json(data, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "unknown"
+
+    @pytest.mark.parametrize(
+        ("mb_type", "subclass"),
+        [
+            ("commands", MenuButtonCommands),
+            ("web_app", MenuButtonWebApp),
+            ("default", MenuButtonDefault),
+        ],
+    )
+    def test_de_json_subclass(self, offline_bot, mb_type, subclass):
+        json_dict = {
+            "type": mb_type,
+            "web_app": self.web_app.to_dict(),
+            "text": self.text,
+        }
+        mb = MenuButton.de_json(json_dict, offline_bot)
+
+        assert type(mb) is subclass
+        assert set(mb.api_kwargs.keys()) == set(json_dict.keys()) - set(subclass.__slots__) - {
+            "type"
+        }
+        assert mb.type == mb_type
 
     def test_to_dict(self, menu_button):
-        menu_button_dict = menu_button.to_dict()
+        assert menu_button.to_dict() == {"type": menu_button.type}
 
-        assert isinstance(menu_button_dict, dict)
-        assert menu_button_dict["type"] == menu_button.type
-        if hasattr(menu_button, "web_app"):
-            assert menu_button_dict["web_app"] == menu_button.web_app.to_dict()
-        if hasattr(menu_button, "text"):
-            assert menu_button_dict["text"] == menu_button.text
+    def test_equality(self, menu_button):
+        a = menu_button
+        b = MenuButton(self.type)
+        c = MenuButton("unknown")
+        d = Dice(5, "test")
 
-    def test_type_enum_conversion(self):
-        assert type(MenuButton("commands").type) is MenuButtonType
-        assert MenuButton("unknown").type == "unknown"
+        assert a == b
+        assert hash(a) == hash(b)
 
-    def test_equality(self, menu_button, offline_bot):
-        a = MenuButton("base_type")
-        b = MenuButton("base_type")
-        c = menu_button
-        d = deepcopy(menu_button)
-        e = Dice(4, "emoji")
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def menu_button_commands():
+    return MenuButtonCommands()
+
+
+class TestMenuButtonCommandsWithoutRequest(MenuButtonTestBase):
+    type = MenuButtonType.COMMANDS
+
+    def test_slot_behaviour(self, menu_button_commands):
+        inst = menu_button_commands
+        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(self, offline_bot):
+        transaction_partner = MenuButtonCommands.de_json({}, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "commands"
+
+    def test_to_dict(self, menu_button_commands):
+        assert menu_button_commands.to_dict() == {"type": menu_button_commands.type}
+
+    def test_equality(self, menu_button_commands):
+        a = menu_button_commands
+        b = MenuButtonCommands()
+        c = Dice(5, "test")
+        d = MenuButtonDefault()
 
         assert a == b
         assert hash(a) == hash(b)
@@ -170,27 +136,92 @@ def test_equality(self, menu_button, offline_bot):
         assert a != d
         assert hash(a) != hash(d)
 
-        assert a != e
-        assert hash(a) != hash(e)
 
-        assert c == d
-        assert hash(c) == hash(d)
+@pytest.fixture
+def menu_button_default():
+    return MenuButtonDefault()
 
-        assert c != e
-        assert hash(c) != hash(e)
 
-        if hasattr(c, "web_app"):
-            json_dict = c.to_dict()
-            json_dict["web_app"] = WebAppInfo("https://foo.bar/web_app").to_dict()
-            f = c.__class__.de_json(json_dict, offline_bot)
+class TestMenuButtonDefaultWithoutRequest(MenuButtonTestBase):
+    type = MenuButtonType.DEFAULT
 
-            assert c != f
-            assert hash(c) != hash(f)
+    def test_slot_behaviour(self, menu_button_default):
+        inst = menu_button_default
+        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"
 
-        if hasattr(c, "text"):
-            json_dict = c.to_dict()
-            json_dict["text"] = "other text"
-            g = c.__class__.de_json(json_dict, offline_bot)
+    def test_de_json(self, offline_bot):
+        transaction_partner = MenuButtonDefault.de_json({}, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "default"
 
-            assert c != g
-            assert hash(c) != hash(g)
+    def test_to_dict(self, menu_button_default):
+        assert menu_button_default.to_dict() == {"type": menu_button_default.type}
+
+    def test_equality(self, menu_button_default):
+        a = menu_button_default
+        b = MenuButtonDefault()
+        c = Dice(5, "test")
+        d = MenuButtonCommands()
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def menu_button_web_app():
+    return MenuButtonWebApp(
+        web_app=TestMenuButtonWebAppWithoutRequest.web_app,
+        text=TestMenuButtonWebAppWithoutRequest.text,
+    )
+
+
+class TestMenuButtonWebAppWithoutRequest(MenuButtonTestBase):
+    type = MenuButtonType.WEB_APP
+
+    def test_slot_behaviour(self, menu_button_web_app):
+        inst = menu_button_web_app
+        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(self, offline_bot):
+        json_dict = {"web_app": self.web_app.to_dict(), "text": self.text}
+        transaction_partner = MenuButtonWebApp.de_json(json_dict, offline_bot)
+        assert transaction_partner.api_kwargs == {}
+        assert transaction_partner.type == "web_app"
+        assert transaction_partner.web_app == self.web_app
+        assert transaction_partner.text == self.text
+
+    def test_to_dict(self, menu_button_web_app):
+        assert menu_button_web_app.to_dict() == {
+            "type": menu_button_web_app.type,
+            "web_app": menu_button_web_app.web_app.to_dict(),
+            "text": menu_button_web_app.text,
+        }
+
+    def test_equality(self, menu_button_web_app):
+        a = menu_button_web_app
+        b = MenuButtonWebApp(web_app=self.web_app, text=self.text)
+        c = MenuButtonWebApp(web_app=self.web_app, text="other text")
+        d = MenuButtonWebApp(web_app=WebAppInfo(url="https://example.org"), text=self.text)
+        e = Dice(5, "test")
+
+        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)
diff --git a/tests/test_paidmedia.py b/tests/test_paidmedia.py
index e6c22959dc0..a696c416b58 100644
--- a/tests/test_paidmedia.py
+++ b/tests/test_paidmedia.py
@@ -37,102 +37,13 @@
 from tests.auxil.slots import mro_slots
 
 
-@pytest.fixture(
-    scope="module",
-    params=[
-        PaidMedia.PREVIEW,
-        PaidMedia.PHOTO,
-        PaidMedia.VIDEO,
-    ],
-)
-def pm_scope_type(request):
-    return request.param
-
-
-@pytest.fixture(
-    scope="module",
-    params=[
-        PaidMediaPreview,
-        PaidMediaPhoto,
-        PaidMediaVideo,
-    ],
-    ids=[
-        PaidMedia.PREVIEW,
-        PaidMedia.PHOTO,
-        PaidMedia.VIDEO,
-    ],
-)
-def pm_scope_class(request):
-    return request.param
-
-
-@pytest.fixture(
-    scope="module",
-    params=[
-        (
-            PaidMediaPreview,
-            PaidMedia.PREVIEW,
-        ),
-        (
-            PaidMediaPhoto,
-            PaidMedia.PHOTO,
-        ),
-        (
-            PaidMediaVideo,
-            PaidMedia.VIDEO,
-        ),
-    ],
-    ids=[
-        PaidMedia.PREVIEW,
-        PaidMedia.PHOTO,
-        PaidMedia.VIDEO,
-    ],
-)
-def pm_scope_class_and_type(request):
-    return request.param
-
-
-@pytest.fixture(scope="module")
-def paid_media(pm_scope_class_and_type):
-    # We use de_json here so that we don't have to worry about which class gets which arguments
-    return pm_scope_class_and_type[0].de_json(
-        {
-            "type": pm_scope_class_and_type[1],
-            "width": PaidMediaTestBase.width,
-            "height": PaidMediaTestBase.height,
-            "duration": PaidMediaTestBase.duration,
-            "video": PaidMediaTestBase.video.to_dict(),
-            "photo": [p.to_dict() for p in PaidMediaTestBase.photo],
-        },
-        bot=None,
-    )
-
-
-def paid_media_video():
-    return PaidMediaVideo(video=PaidMediaTestBase.video)
-
-
-def paid_media_photo():
-    return PaidMediaPhoto(photo=PaidMediaTestBase.photo)
-
-
-@pytest.fixture(scope="module")
-def paid_media_info():
-    return PaidMediaInfo(
-        star_count=PaidMediaInfoTestBase.star_count,
-        paid_media=[paid_media_video(), paid_media_photo()],
-    )
-
-
-@pytest.fixture(scope="module")
-def paid_media_purchased():
-    return PaidMediaPurchased(
-        from_user=PaidMediaPurchasedTestBase.from_user,
-        paid_media_payload=PaidMediaPurchasedTestBase.paid_media_payload,
-    )
+@pytest.fixture
+def paid_media():
+    return PaidMedia(type=PaidMediaType.PHOTO)
 
 
 class PaidMediaTestBase:
+    type = PaidMediaType.PHOTO
     width = 640
     height = 480
     duration = 60
@@ -160,97 +71,207 @@ def test_slot_behaviour(self, paid_media):
             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(self, offline_bot, pm_scope_class_and_type):
-        cls = pm_scope_class_and_type[0]
-        type_ = pm_scope_class_and_type[1]
+    def test_type_enum_conversion(self, paid_media):
+        assert type(PaidMedia("photo").type) is PaidMediaType
+        assert PaidMedia("unknown").type == "unknown"
 
+    def test_de_json(self, offline_bot):
+        data = {"type": "unknown"}
+        paid_media = PaidMedia.de_json(data, offline_bot)
+        assert paid_media.api_kwargs == {}
+        assert paid_media.type == "unknown"
+
+    @pytest.mark.parametrize(
+        ("pm_type", "subclass"),
+        [
+            ("photo", PaidMediaPhoto),
+            ("video", PaidMediaVideo),
+            ("preview", PaidMediaPreview),
+        ],
+    )
+    def test_de_json_subclass(self, offline_bot, pm_type, subclass):
         json_dict = {
-            "type": type_,
+            "type": pm_type,
+            "video": self.video.to_dict(),
+            "photo": [p.to_dict() for p in self.photo],
             "width": self.width,
             "height": self.height,
             "duration": self.duration,
-            "video": self.video.to_dict(),
-            "photo": [p.to_dict() for p in self.photo],
         }
         pm = PaidMedia.de_json(json_dict, offline_bot)
-        assert set(pm.api_kwargs.keys()) == {
-            "width",
-            "height",
-            "duration",
-            "video",
-            "photo",
-        } - set(cls.__slots__)
-
-        assert isinstance(pm, PaidMedia)
-        assert type(pm) is cls
-        assert pm.type == type_
-        if "width" in cls.__slots__:
-            assert pm.width == self.width
-            assert pm.height == self.height
-            assert pm.duration == self.duration
-        if "video" in cls.__slots__:
-            assert pm.video == self.video
-        if "photo" in cls.__slots__:
-            assert pm.photo == self.photo
-
-    def test_de_json_invalid_type(self, offline_bot):
+
+        assert type(pm) is subclass
+        assert set(pm.api_kwargs.keys()) == set(json_dict.keys()) - set(subclass.__slots__) - {
+            "type"
+        }
+        assert pm.type == pm_type
+
+    def test_to_dict(self, paid_media):
+        assert paid_media.to_dict() == {"type": paid_media.type}
+
+    def test_equality(self, paid_media):
+        a = paid_media
+        b = PaidMedia(self.type)
+        c = PaidMedia("unknown")
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def paid_media_photo():
+    return PaidMediaPhoto(
+        photo=TestPaidMediaPhotoWithoutRequest.photo,
+    )
+
+
+class TestPaidMediaPhotoWithoutRequest(PaidMediaTestBase):
+    type = PaidMediaType.PHOTO
+
+    def test_slot_behaviour(self, paid_media_photo):
+        inst = paid_media_photo
+        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(self, offline_bot):
         json_dict = {
-            "type": "invalid",
-            "width": self.width,
-            "height": self.height,
-            "duration": self.duration,
-            "video": self.video.to_dict(),
             "photo": [p.to_dict() for p in self.photo],
         }
-        pm = PaidMedia.de_json(json_dict, offline_bot)
-        assert pm.api_kwargs == {
-            "width": self.width,
-            "height": self.height,
-            "duration": self.duration,
-            "video": self.video.to_dict(),
+        pmp = PaidMediaPhoto.de_json(json_dict, offline_bot)
+        assert pmp.photo == tuple(self.photo)
+        assert pmp.api_kwargs == {}
+
+    def test_to_dict(self, paid_media_photo):
+        assert paid_media_photo.to_dict() == {
+            "type": paid_media_photo.type,
             "photo": [p.to_dict() for p in self.photo],
         }
 
-        assert type(pm) is PaidMedia
-        assert pm.type == "invalid"
+    def test_equality(self, paid_media_photo):
+        a = paid_media_photo
+        b = PaidMediaPhoto(deepcopy(self.photo))
+        c = PaidMediaPhoto([PhotoSize("file_id", 640, 480, "file_unique_id")])
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def paid_media_video():
+    return PaidMediaVideo(
+        video=TestPaidMediaVideoWithoutRequest.video,
+    )
+
+
+class TestPaidMediaVideoWithoutRequest(PaidMediaTestBase):
+    type = PaidMediaType.VIDEO
+
+    def test_slot_behaviour(self, paid_media_video):
+        inst = paid_media_video
+        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_subclass(self, pm_scope_class, offline_bot):
-        """This makes sure that e.g. PaidMediaPreivew(data) never returns a
-        TransactionPartnerPhoto instance."""
+    def test_de_json(self, offline_bot):
+        json_dict = {
+            "video": self.video.to_dict(),
+        }
+        pmv = PaidMediaVideo.de_json(json_dict, offline_bot)
+        assert pmv.video == self.video
+        assert pmv.api_kwargs == {}
+
+    def test_to_dict(self, paid_media_video):
+        assert paid_media_video.to_dict() == {
+            "type": self.type,
+            "video": paid_media_video.video.to_dict(),
+        }
+
+    def test_equality(self, paid_media_video):
+        a = paid_media_video
+        b = PaidMediaVideo(
+            video=deepcopy(self.video),
+        )
+        c = PaidMediaVideo(
+            video=Video("test", "test_unique", 640, 480, 60),
+        )
+        d = Dice(5, "test")
+
+        assert a == b
+        assert hash(a) == hash(b)
+
+        assert a != c
+        assert hash(a) != hash(c)
+
+        assert a != d
+        assert hash(a) != hash(d)
+
+
+@pytest.fixture
+def paid_media_preview():
+    return PaidMediaPreview(
+        width=TestPaidMediaPreviewWithoutRequest.width,
+        height=TestPaidMediaPreviewWithoutRequest.height,
+        duration=TestPaidMediaPreviewWithoutRequest.duration,
+    )
+
+
+class TestPaidMediaPreviewWithoutRequest(PaidMediaTestBase):
+    type = PaidMediaType.PREVIEW
+
+    def test_slot_behaviour(self, paid_media_preview):
+        inst = paid_media_preview
+        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(self, offline_bot):
         json_dict = {
-            "type": "invalid",
             "width": self.width,
             "height": self.height,
             "duration": self.duration,
-            "video": self.video.to_dict(),
-            "photo": [p.to_dict() for p in self.photo],
         }
-        assert type(pm_scope_class.de_json(json_dict, offline_bot)) is pm_scope_class
+        pmp = PaidMediaPreview.de_json(json_dict, offline_bot)
+        assert pmp.width == self.width
+        assert pmp.height == self.height
+        assert pmp.duration == self.duration
+        assert pmp.api_kwargs == {}
 
-    def test_to_dict(self, paid_media):
-        pm_dict = paid_media.to_dict()
-
-        assert isinstance(pm_dict, dict)
-        assert pm_dict["type"] == paid_media.type
-        if hasattr(paid_media_info, "width"):
-            assert pm_dict["width"] == paid_media.width
-            assert pm_dict["height"] == paid_media.height
-            assert pm_dict["duration"] == paid_media.duration
-        if hasattr(paid_media_info, "video"):
-            assert pm_dict["video"] == paid_media.video.to_dict()
-        if hasattr(paid_media_info, "photo"):
-            assert pm_dict["photo"] == [p.to_dict() for p in paid_media.photo]
-
-    def test_type_enum_conversion(self):
-        assert type(PaidMedia("video").type) is PaidMediaType
-        assert PaidMedia("unknown").type == "unknown"
+    def test_to_dict(self, paid_media_preview):
+        assert paid_media_preview.to_dict() == {
+            "type": paid_media_preview.type,
+            "width": self.width,
+            "height": self.height,
+            "duration": self.duration,
+        }
 
-    def test_equality(self, paid_media, offline_bot):
-        a = PaidMedia("base_type")
-        b = PaidMedia("base_type")
-        c = paid_media
-        d = deepcopy(paid_media)
-        e = Dice(4, "emoji")
+    def test_equality(self, paid_media_preview):
+        a = paid_media_preview
+        b = PaidMediaPreview(
+            width=self.width,
+            height=self.height,
+            duration=self.duration,
+        )
+        c = PaidMediaPreview(
+            width=100,
+            height=100,
+            duration=100,
+        )
+        d = Dice(5, "test")
 
         assert a == b
         assert hash(a) == hash(b)
@@ -261,35 +282,58 @@ def test_equality(self, paid_media, offline_bot):
         assert a != d
         assert hash(a) != hash(d)
 
-        assert a != e
-        assert hash(a) != hash(e)
-
-        assert c == d
-        assert hash(c) == hash(d)
 
-        assert c != e
-        assert hash(c) != hash(e)
+# ===========================================================================================
+# ===========================================================================================
+# ===========================================================================================
+# ===========================================================================================
+# ===========================================================================================
+# ===========================================================================================
+# ===========================================================================================
+# ===========================================================================================
+# ===========================================================================================
+# ===========================================================================================
 
-        if hasattr(c, "video"):
-            json_dict = c.to_dict()
-            json_dict["video"] = Video("different", "d2", 1, 1, 1).to_dict()
-            f = c.__class__.de_json(json_dict, offline_bot)
 
-            assert c != f
-            assert hash(c) != hash(f)
+@pytest.fixture(scope="module")
+def paid_media_info():
+    return PaidMediaInfo(
+        star_count=PaidMediaInfoTestBase.star_count,
+        paid_media=PaidMediaInfoTestBase.paid_media,
+    )
 
-        if hasattr(c, "photo"):
-            json_dict = c.to_dict()
-            json_dict["photo"] = [PhotoSize("different", "d2", 1, 1, 1).to_dict()]
-            f = c.__class__.de_json(json_dict, offline_bot)
 
-            assert c != f
-            assert hash(c) != hash(f)
+@pytest.fixture(scope="module")
+def paid_media_purchased():
+    return PaidMediaPurchased(
+        from_user=PaidMediaPurchasedTestBase.from_user,
+        paid_media_payload=PaidMediaPurchasedTestBase.paid_media_payload,
+    )
 
 
 class PaidMediaInfoTestBase:
     star_count = 200
-    paid_media = [paid_media_video(), paid_media_photo()]
+    paid_media = [
+        PaidMediaVideo(
+            video=Video(
+                file_id="video_file_id",
+                width=640,
+                height=480,
+                file_unique_id="file_unique_id",
+                duration=60,
+            )
+        ),
+        PaidMediaPhoto(
+            photo=[
+                PhotoSize(
+                    file_id="photo_file_id",
+                    width=640,
+                    height=480,
+                    file_unique_id="file_unique_id",
+                )
+            ]
+        ),
+    ]
 
 
 class TestPaidMediaInfoWithoutRequest(PaidMediaInfoTestBase):
@@ -315,13 +359,9 @@ def test_to_dict(self, paid_media_info):
         }
 
     def test_equality(self):
-        pmi1 = PaidMediaInfo(
-            star_count=self.star_count, paid_media=[paid_media_video(), paid_media_photo()]
-        )
-        pmi2 = PaidMediaInfo(
-            star_count=self.star_count, paid_media=[paid_media_video(), paid_media_photo()]
-        )
-        pmi3 = PaidMediaInfo(star_count=100, paid_media=[paid_media_photo()])
+        pmi1 = PaidMediaInfo(star_count=self.star_count, paid_media=self.paid_media)
+        pmi2 = PaidMediaInfo(star_count=self.star_count, paid_media=self.paid_media)
+        pmi3 = PaidMediaInfo(star_count=100, paid_media=[self.paid_media[0]])
 
         assert pmi1 == pmi2
         assert hash(pmi1) == hash(pmi2)
diff --git a/tests/test_reaction.py b/tests/test_reaction.py
index 8c209500ed2..af4e3f6fb15 100644
--- a/tests/test_reaction.py
+++ b/tests/test_reaction.py
@@ -16,8 +16,6 @@
 #
 # 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 inspect
-from copy import deepcopy
 
 import pytest
 
@@ -29,161 +27,148 @@
     ReactionTypeCustomEmoji,
     ReactionTypeEmoji,
     ReactionTypePaid,
+    constants,
 )
 from telegram.constants import ReactionEmoji
 from tests.auxil.slots import mro_slots
 
-ignored = ["self", "api_kwargs"]
 
+@pytest.fixture(scope="module")
+def reaction_type():
+    return ReactionType(type=TestReactionTypeWithoutRequest.type)
 
-class RTDefaults:
-    custom_emoji = "123custom"
-    normal_emoji = ReactionEmoji.THUMBS_UP
 
+class ReactionTypeTestBase:
+    type = "emoji"
+    emoji = "some_emoji"
+    custom_emoji_id = "some_custom_emoji_id"
 
-def reaction_type_custom_emoji():
-    return ReactionTypeCustomEmoji(RTDefaults.custom_emoji)
 
+class TestReactionTypeWithoutRequest(ReactionTypeTestBase):
+    def test_slot_behaviour(self, reaction_type):
+        inst = reaction_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 reaction_type_emoji():
-    return ReactionTypeEmoji(RTDefaults.normal_emoji)
+    def test_type_enum_conversion(self):
+        assert type(ReactionType("emoji").type) is constants.ReactionType
+        assert ReactionType("unknown").type == "unknown"
 
+    def test_de_json(self, offline_bot):
+        json_dict = {"type": "unknown"}
+        reaction_type = ReactionType.de_json(json_dict, offline_bot)
+        assert reaction_type.api_kwargs == {}
+        assert reaction_type.type == "unknown"
+
+    @pytest.mark.parametrize(
+        ("rt_type", "subclass"),
+        [
+            ("emoji", ReactionTypeEmoji),
+            ("custom_emoji", ReactionTypeCustomEmoji),
+            ("paid", ReactionTypePaid),
+        ],
+    )
+    def test_de_json_subclass(self, offline_bot, rt_type, subclass):
+        json_dict = {
+            "type": rt_type,
+            "emoji": self.emoji,
+            "custom_emoji_id": self.custom_emoji_id,
+        }
+        rt = ReactionType.de_json(json_dict, offline_bot)
 
-def reaction_type_paid():
-    return ReactionTypePaid()
+        assert type(rt) is subclass
+        assert set(rt.api_kwargs.keys()) == set(json_dict.keys()) - set(subclass.__slots__) - {
+            "type"
+        }
+        assert rt.type == rt_type
 
+    def test_to_dict(self, reaction_type):
+        reaction_type_dict = reaction_type.to_dict()
+        assert isinstance(reaction_type_dict, dict)
+        assert reaction_type_dict["type"] == reaction_type.type
 
-def make_json_dict(instance: ReactionType, 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 = {"type": instance.type}
-    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)-
-        # currently not needed, keeping for completeness
-        elif param.default is not inspect.Parameter.empty and include_optional_args:
-            json_dict[param.name] = val
-    return json_dict
-
-
-def iter_args(instance: ReactionType, de_json_inst: ReactionType, 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.type, de_json_inst.type  # 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 (
-            param.default is not inspect.Parameter.empty and include_optional
-        ) or param.default is inspect.Parameter.empty:
-            yield inst_at, json_at
-
-
-@pytest.fixture
-def reaction_type(request):
-    return request.param()
-
-
-@pytest.mark.parametrize(
-    "reaction_type",
-    [
-        reaction_type_custom_emoji,
-        reaction_type_emoji,
-        reaction_type_paid,
-    ],
-    indirect=True,
-)
-class TestReactionTypesWithoutRequest:
-    def test_slot_behaviour(self, reaction_type):
-        inst = reaction_type
+
+@pytest.fixture(scope="module")
+def reaction_type_emoji():
+    return ReactionTypeEmoji(emoji=TestReactionTypeEmojiWithoutRequest.emoji)
+
+
+class TestReactionTypeEmojiWithoutRequest(ReactionTypeTestBase):
+    type = constants.ReactionType.EMOJI
+
+    def test_slot_behaviour(self, reaction_type_emoji):
+        inst = reaction_type_emoji
         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, offline_bot, reaction_type):
-        cls = reaction_type.__class__
+    def test_de_json(self, offline_bot):
+        json_dict = {"emoji": self.emoji}
+        reaction_type_emoji = ReactionTypeEmoji.de_json(json_dict, offline_bot)
+        assert reaction_type_emoji.api_kwargs == {}
+        assert reaction_type_emoji.type == self.type
+        assert reaction_type_emoji.emoji == self.emoji
+
+    def test_to_dict(self, reaction_type_emoji):
+        reaction_type_emoji_dict = reaction_type_emoji.to_dict()
+        assert isinstance(reaction_type_emoji_dict, dict)
+        assert reaction_type_emoji_dict["type"] == reaction_type_emoji.type
+        assert reaction_type_emoji_dict["emoji"] == reaction_type_emoji.emoji
+
+    def test_equality(self, reaction_type_emoji):
+        a = reaction_type_emoji
+        b = ReactionTypeEmoji(emoji=self.emoji)
+        c = ReactionTypeEmoji(emoji="other_emoji")
+        d = Dice(5, "test")
 
-        json_dict = make_json_dict(reaction_type)
-        const_reaction_type = ReactionType.de_json(json_dict, offline_bot)
-        assert const_reaction_type.api_kwargs == {}
+        assert a == b
+        assert hash(a) == hash(b)
 
-        assert isinstance(const_reaction_type, ReactionType)
-        assert isinstance(const_reaction_type, cls)
-        for reaction_type_at, const_reaction_type_at in iter_args(
-            reaction_type, const_reaction_type
-        ):
-            assert reaction_type_at == const_reaction_type_at
+        assert a != c
+        assert hash(a) != hash(c)
 
-    def test_de_json_all_args(self, offline_bot, reaction_type):
-        json_dict = make_json_dict(reaction_type, include_optional_args=True)
-        const_reaction_type = ReactionType.de_json(json_dict, offline_bot)
-        assert const_reaction_type.api_kwargs == {}
+        assert a != d
+        assert hash(a) != hash(d)
 
-        assert isinstance(const_reaction_type, ReactionType)
-        assert isinstance(const_reaction_type, reaction_type.__class__)
-        for c_mem_type_at, const_c_mem_at in iter_args(reaction_type, const_reaction_type, True):
-            assert c_mem_type_at == const_c_mem_at
 
-    def test_de_json_invalid_type(self, offline_bot, reaction_type):
-        json_dict = {"type": "invalid"}
-        reaction_type = ReactionType.de_json(json_dict, offline_bot)
+@pytest.fixture(scope="module")
+def reaction_type_custom_emoji():
+    return ReactionTypeCustomEmoji(
+        custom_emoji_id=TestReactionTypeCustomEmojiWithoutRequest.custom_emoji_id
+    )
 
-        assert type(reaction_type) is ReactionType
-        assert reaction_type.type == "invalid"
 
-    def test_de_json_subclass(self, reaction_type, offline_bot, chat_id):
-        """This makes sure that e.g. ReactionTypeEmoji(data, offline_bot) never returns a
-        ReactionTypeCustomEmoji instance."""
-        cls = reaction_type.__class__
-        json_dict = make_json_dict(reaction_type, True)
-        assert type(cls.de_json(json_dict, offline_bot)) is cls
+class TestReactionTypeCustomEmojiWithoutRequest(ReactionTypeTestBase):
+    type = constants.ReactionType.CUSTOM_EMOJI
 
-    def test_to_dict(self, reaction_type):
-        reaction_type_dict = reaction_type.to_dict()
+    def test_slot_behaviour(self, reaction_type_custom_emoji):
+        inst = reaction_type_custom_emoji
+        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"
 
-        assert isinstance(reaction_type_dict, dict)
-        assert reaction_type_dict["type"] == reaction_type.type
-        if reaction_type.type == ReactionType.EMOJI:
-            assert reaction_type_dict["emoji"] == reaction_type.emoji
-        elif reaction_type.type == ReactionType.CUSTOM_EMOJI:
-            assert reaction_type_dict["custom_emoji_id"] == reaction_type.custom_emoji_id
-
-        for slot in reaction_type.__slots__:  # additional verification for the optional args
-            assert getattr(reaction_type, slot) == reaction_type_dict[slot]
-
-    def test_reaction_type_api_kwargs(self, reaction_type):
-        json_dict = make_json_dict(reaction_type_custom_emoji())
-        json_dict["custom_arg"] = "wuhu"
-        reaction_type_custom_emoji_instance = ReactionType.de_json(json_dict, None)
-        assert reaction_type_custom_emoji_instance.api_kwargs == {
-            "custom_arg": "wuhu",
-        }
+    def test_de_json(self, offline_bot):
+        json_dict = {"custom_emoji_id": self.custom_emoji_id}
+        reaction_type_custom_emoji = ReactionTypeCustomEmoji.de_json(json_dict, offline_bot)
+        assert reaction_type_custom_emoji.api_kwargs == {}
+        assert reaction_type_custom_emoji.type == self.type
+        assert reaction_type_custom_emoji.custom_emoji_id == self.custom_emoji_id
+
+    def test_to_dict(self, reaction_type_custom_emoji):
+        reaction_type_custom_emoji_dict = reaction_type_custom_emoji.to_dict()
+        assert isinstance(reaction_type_custom_emoji_dict, dict)
+        assert reaction_type_custom_emoji_dict["type"] == reaction_type_custom_emoji.type
+        assert (
+            reaction_type_custom_emoji_dict["custom_emoji_id"]
+            == reaction_type_custom_emoji.custom_emoji_id
+        )
 
-    def test_equality(self, reaction_type):
-        a = ReactionTypeEmoji(emoji=RTDefaults.normal_emoji)
-        b = ReactionTypeEmoji(emoji=RTDefaults.normal_emoji)
-        c = ReactionTypeCustomEmoji(custom_emoji_id=RTDefaults.custom_emoji)
-        d = ReactionTypeCustomEmoji(custom_emoji_id=RTDefaults.custom_emoji)
-        e = ReactionTypeEmoji(emoji=ReactionEmoji.RED_HEART)
-        f = ReactionTypeCustomEmoji(custom_emoji_id="1234custom")
-        g = deepcopy(a)
-        h = deepcopy(c)
-        i = Dice(4, "emoji")
+    def test_equality(self, reaction_type_custom_emoji):
+        a = reaction_type_custom_emoji
+        b = ReactionTypeCustomEmoji(custom_emoji_id=self.custom_emoji_id)
+        c = ReactionTypeCustomEmoji(custom_emoji_id="other_custom_emoji_id")
+        d = Dice(5, "test")
 
         assert a == b
         assert hash(a) == hash(b)
@@ -191,29 +176,34 @@ def test_equality(self, reaction_type):
         assert a != c
         assert hash(a) != hash(c)
 
-        assert a != e
-        assert hash(a) != hash(e)
-
-        assert a == g
-        assert hash(a) == hash(g)
+        assert a != d
+        assert hash(a) != hash(d)
 
-        assert a != i
-        assert hash(a) != hash(i)
 
-        assert c == d
-        assert hash(c) == hash(d)
+@pytest.fixture(scope="module")
+def reaction_type_paid():
+    return ReactionTypePaid()
 
-        assert c != e
-        assert hash(c) != hash(e)
 
-        assert c != f
-        assert hash(c) != hash(f)
+class TestReactionTypePaidWithoutRequest(ReactionTypeTestBase):
+    type = constants.ReactionType.PAID
 
-        assert c == h
-        assert hash(c) == hash(h)
+    def test_slot_behaviour(self, reaction_type_paid):
+        inst = reaction_type_paid
+        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"
 
-        assert c != i
-        assert hash(c) != hash(i)
+    def test_de_json(self, offline_bot):
+        json_dict = {}
+        reaction_type_paid = ReactionTypePaid.de_json(json_dict, offline_bot)
+        assert reaction_type_paid.api_kwargs == {}
+        assert reaction_type_paid.type == self.type
+
+    def test_to_dict(self, reaction_type_paid):
+        reaction_type_paid_dict = reaction_type_paid.to_dict()
+        assert isinstance(reaction_type_paid_dict, dict)
+        assert reaction_type_paid_dict["type"] == reaction_type_paid.type
 
 
 @pytest.fixture(scope="module")