Repository: marshmallow-code/marshmallow
Pull request #2755 (2025-01-11 23:18:00)

Run mypy against tests
to discover invalid usages and typing issues.

- run mypy within tox instead of pre-commit so package gets installed (type-checking was incorrect when run through pre-commit)
- fix a few type issues in the code (will backport these to 3.x-line)
- address all mypy errors in tests


Patch:
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index 14332de4..4045bdaa 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -6,17 +6,6 @@ on:
   pull_request:
 
 jobs:
-  mypy:
-    name: mypy
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4.0.0
-      - uses: actions/setup-python@v5
-        with:
-          python-version: "3.13"
-      - run: python -m pip install --upgrade pip
-      - run: python -m pip install tox
-      - run: python -m tox -e mypy-test
   docs:
     name: docs
     runs-on: ubuntu-latest
@@ -36,6 +25,7 @@ jobs:
         include:
           - { name: "3.9", python: "3.9", tox: py39 }
           - { name: "3.13", python: "3.13", tox: py313 }
+          - { name: "mypy", python: "3.13", tox: mypy }
     steps:
       - uses: actions/checkout@v4.0.0
       - uses: actions/setup-python@v5
@@ -80,7 +70,7 @@ jobs:
   publish-to-pypi:
     name: PyPI release
     if: startsWith(github.ref, 'refs/tags/')
-    needs: [mypy, build, tests, lint-pre-release]
+    needs: [build, tests, lint-pre-release]
     runs-on: ubuntu-latest
     environment:
       name: pypi
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b67cb6de..c99278f0 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,10 +17,3 @@ repos:
   hooks:
   - id: blacken-docs
     additional_dependencies: [black==24.10.0]
-- repo: https://github.com/pre-commit/mirrors-mypy
-  rev: v1.14.1
-  hooks:
-  - id: mypy
-    additional_dependencies: [types-simplejson, packaging]
-    # these files are checked under `tox -e mypy-test`
-    exclude: ^tests/mypy_test_cases/.*$
diff --git a/pyproject.toml b/pyproject.toml
index 1ba77526..598926f5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -92,6 +92,11 @@ warn_unused_ignores = true
 warn_redundant_casts = true
 no_implicit_optional = true
 
+[[tool.mypy.overrides]]
+module = "tests.*"
+check_untyped_defs = true
+disable_error_code = ["call-overload", "index"]
+
 [tool.pytest.ini_options]
 norecursedirs = ".git .ropeproject .tox docs env venv tests/mypy_test_cases"
 addopts = "-v --tb=short"
diff --git a/src/marshmallow/class_registry.py b/src/marshmallow/class_registry.py
index a8e2d6cd..18bbd35c 100644
--- a/src/marshmallow/class_registry.py
+++ b/src/marshmallow/class_registry.py
@@ -74,9 +74,7 @@ def get_class(classname: str, all: typing.Literal[False] = ...) -> SchemaType: .
 
 
 @typing.overload
-def get_class(
-    classname: str, all: typing.Literal[True] = ...
-) -> list[SchemaType] | SchemaType: ...
+def get_class(classname: str, all: typing.Literal[True] = ...) -> list[SchemaType]: ...
 
 
 def get_class(classname: str, all: bool = False) -> list[SchemaType] | SchemaType:
diff --git a/src/marshmallow/fields.py b/src/marshmallow/fields.py
index 64fc2e9d..3e0411e7 100644
--- a/src/marshmallow/fields.py
+++ b/src/marshmallow/fields.py
@@ -654,9 +654,14 @@ class Pluck(Nested):
         self,
         nested: Schema | SchemaMeta | str | typing.Callable[[], Schema],
         field_name: str,
+        *,
+        many: bool = False,
+        unknown: str | None = None,
         **kwargs: Unpack[_BaseFieldKwargs],
     ):
-        super().__init__(nested, only=(field_name,), **kwargs)
+        super().__init__(
+            nested, only=(field_name,), many=many, unknown=unknown, **kwargs
+        )
         self.field_name = field_name
 
     @property
@@ -771,7 +776,9 @@ class Tuple(Field[tuple]):
     default_error_messages = {"invalid": "Not a valid tuple."}
 
     def __init__(
-        self, tuple_fields: typing.Iterable[Field], **kwargs: Unpack[_BaseFieldKwargs]
+        self,
+        tuple_fields: typing.Iterable[Field] | typing.Iterable[type[Field]],
+        **kwargs: Unpack[_BaseFieldKwargs],
     ):
         super().__init__(**kwargs)
         if not utils.is_collection(tuple_fields):
@@ -1155,8 +1162,8 @@ class Boolean(Field[bool]):
     def __init__(
         self,
         *,
-        truthy: set | None = None,
-        falsy: set | None = None,
+        truthy: typing.Iterable | None = None,
+        falsy: typing.Iterable | None = None,
         **kwargs: Unpack[_BaseFieldKwargs],
     ):
         super().__init__(**kwargs)
@@ -1522,7 +1529,7 @@ class TimeDelta(Field[dt.timedelta]):
             raise self.make_error("invalid") from error
 
 
-_MappingType = typing.TypeVar("_MappingType", bound=collections.abc.Mapping)
+_MappingType = typing.TypeVar("_MappingType", bound=_Mapping)
 
 
 class Mapping(Field[_MappingType]):
diff --git a/tox.ini b/tox.ini
index d1dd7a67..e3c03c0a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = lint,mypy-test,py{39,310,311,312,313},docs
+envlist = lint,mypy,py{39,310,311,312,313},docs
 
 [testenv]
 extras = tests
@@ -10,9 +10,11 @@ deps = pre-commit~=3.5
 skip_install = true
 commands = pre-commit run --all-files
 
-[testenv:mypy-test]
-deps = mypy
-commands = mypy --show-error-codes --warn-unused-ignores tests/mypy_test_cases/
+[testenv:mypy]
+deps = 
+  mypy
+  types-simplejson
+commands = mypy --show-error-codes .
 
 [testenv:docs]
 extras = docs


Test patch:
diff --git a/tests/base.py b/tests/base.py
index c4c0cadf..28efaeff 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -177,7 +177,7 @@ class DummyModel:
 class Uppercased(fields.String):
     """Custom field formatting example."""
 
-    def _serialize(self, value, attr, obj):
+    def _serialize(self, value, attr, obj, **kwargs):
         if value:
             return value.upper()
 
@@ -186,7 +186,7 @@ def get_lowername(obj):
     if obj is None:
         return missing
     if isinstance(obj, dict):
-        return obj.get("name").lower()
+        return obj.get("name", "").lower()
     else:
         return obj.name.lower()
 
@@ -228,7 +228,7 @@ class UserSchema(Schema):
         if obj is None:
             return missing
         if isinstance(obj, dict):
-            age = obj.get("age")
+            age = obj.get("age", 0)
         else:
             age = obj.age
         try:
diff --git a/tests/test_context.py b/tests/test_context.py
index 53fa8347..1be941a5 100644
--- a/tests/test_context.py
+++ b/tests/test_context.py
@@ -31,13 +31,13 @@ class TestContext:
     def test_context_load_dump(self):
         class ContextField(fields.Integer):
             def _serialize(self, value, attr, obj, **kwargs):
-                if (context := Context.get(None)) is not None:
+                if (context := Context[dict].get(None)) is not None:
                     value *= context.get("factor", 1)
                 return super()._serialize(value, attr, obj, **kwargs)
 
             def _deserialize(self, value, attr, data, **kwargs):
                 val = super()._deserialize(value, attr, data, **kwargs)
-                if (context := Context.get(None)) is not None:
+                if (context := Context[dict].get(None)) is not None:
                     val *= context.get("factor", 1)
                 return val
 
@@ -100,19 +100,19 @@ class TestContext:
             inner = fields.Nested(InnerSchema)
 
         ser = CSchema()
-        with Context({"info": "i like bikes"}):
-            obj = {"inner": {}}
+        with Context[dict]({"info": "i like bikes"}):
+            obj: dict[str, dict] = {"inner": {}}
             result = ser.dump(obj)
             assert result["inner"]["likes_bikes"] is True
 
     # Regression test for https://github.com/marshmallow-code/marshmallow/issues/820
     def test_nested_list_fields_inherit_context(self):
         class InnerSchema(Schema):
-            foo = fields.Field()
+            foo = fields.Raw()
 
             @validates("foo")
             def validate_foo(self, value):
-                if "foo_context" not in Context.get():
+                if "foo_context" not in Context[dict].get():
                     raise ValidationError("Missing context")
 
         class OuterSchema(Schema):
@@ -129,11 +129,11 @@ class TestContext:
     # Regression test for https://github.com/marshmallow-code/marshmallow/issues/820
     def test_nested_dict_fields_inherit_context(self):
         class InnerSchema(Schema):
-            foo = fields.Field()
+            foo = fields.Raw()
 
             @validates("foo")
             def validate_foo(self, value):
-                if "foo_context" not in Context.get():
+                if "foo_context" not in Context[dict].get():
                     raise ValidationError("Missing context")
 
         class OuterSchema(Schema):
@@ -154,7 +154,7 @@ class TestContext:
                 raise NotImplementedError
 
         class InnerSchema(Schema):
-            foo = fields.Field()
+            foo = fields.Raw()
 
         class OuterSchema(Schema):
             inner = fields.Nested(InnerSchema())
diff --git a/tests/test_decorators.py b/tests/test_decorators.py
index 043b2b33..88019a46 100644
--- a/tests/test_decorators.py
+++ b/tests/test_decorators.py
@@ -119,10 +119,10 @@ def test_decorated_processor_returning_none(unknown):
     schema = PostSchema(unknown=unknown)
     assert schema.dump({"value": 3}) is None
     assert schema.load({"value": 3}) is None
-    schema = PreSchema(unknown=unknown)
-    assert schema.dump({"value": 3}) == {}
+    pre_schema = PreSchema(unknown=unknown)
+    assert pre_schema.dump({"value": 3}) == {}
     with pytest.raises(ValidationError) as excinfo:
-        schema.load({"value": 3})
+        pre_schema.load({"value": 3})
     assert excinfo.value.messages == {"_schema": ["Invalid input type."]}
 
 
@@ -306,21 +306,23 @@ class TestValidatesDecorator:
 
         with pytest.raises(ValidationError) as excinfo:
             schema.load({"foo": 41})
-        errors = excinfo.value.messages
+        assert excinfo.value.messages
         result = excinfo.value.valid_data
-        assert errors
         assert result == {}
 
         with pytest.raises(ValidationError) as excinfo:
             schema.load([{"foo": 42}, {"foo": 43}], many=True)
-        errors = excinfo.value.messages
+        error_messages = excinfo.value.messages
         result = excinfo.value.valid_data
+        assert isinstance(result, list)
         assert len(result) == 2
         assert result[0] == {"foo": 42}
         assert result[1] == {}
-        assert 1 in errors
-        assert "foo" in errors[1]
-        assert errors[1]["foo"] == ["The answer to life the universe and everything."]
+        assert 1 in error_messages
+        assert "foo" in error_messages[1]
+        assert error_messages[1]["foo"] == [
+            "The answer to life the universe and everything."
+        ]
 
     def test_field_not_present(self):
         class BadSchema(ValidatesSchema):
@@ -608,7 +610,7 @@ class TestValidatesSchemaDecorator:
 
             @validates_schema(skip_on_field_errors=True)
             def consistency_validation(self, data, **kwargs):
-                errors = {}
+                errors: dict[str, str | dict] = {}
                 if data["bar"]["baz"] != data["foo"]:
                     errors["bar"] = {"baz": "Non-matching value"}
                 if data["bam"] > data["foo"]:
diff --git a/tests/test_deserialization.py b/tests/test_deserialization.py
index 01c10b50..d840f665 100644
--- a/tests/test_deserialization.py
+++ b/tests/test_deserialization.py
@@ -1,3 +1,4 @@
+# mypy: disable-error-code="arg-type"
 import datetime as dt
 import decimal
 import ipaddress
@@ -31,14 +32,14 @@ from tests.base import (
 class MockDateTimeOverflowError(dt.datetime):
     """Used to simulate the possible OverflowError of datetime.fromtimestamp"""
 
-    def fromtimestamp(self, *args, **kwargs):
+    def fromtimestamp(self, *args, **kwargs):  # type: ignore[override]
         raise OverflowError()
 
 
 class MockDateTimeOSError(dt.datetime):
     """Used to simulate the possible OSError of datetime.fromtimestamp"""
 
-    def fromtimestamp(self, *args, **kwargs):
+    def fromtimestamp(self, *args, **kwargs):  # type: ignore[override]
         raise OSError()
 
 
@@ -358,9 +359,9 @@ class TestFieldDeserialization:
         field = MyBoolean()
         assert field.deserialize("yep") is True
 
-        field = fields.Boolean(truthy=("yep",))
-        assert field.deserialize("yep") is True
-        assert field.deserialize(False) is False
+        field2 = fields.Boolean(truthy=("yep",))
+        assert field2.deserialize("yep") is True
+        assert field2.deserialize(False) is False
 
     @pytest.mark.parametrize("in_val", ["notvalid", 123])
     def test_boolean_field_deserialization_with_custom_truthy_values_invalid(
@@ -375,23 +376,19 @@ class TestFieldDeserialization:
         expected_msg = "Not a valid boolean."
         assert str(excinfo.value.args[0]) == expected_msg
 
-        field = fields.Boolean(truthy=("yep",))
+        field2 = fields.Boolean(truthy={"yep"})
         with pytest.raises(ValidationError) as excinfo:
-            field.deserialize(in_val)
+            field2.deserialize(in_val)
         expected_msg = "Not a valid boolean."
         assert str(excinfo.value.args[0]) == expected_msg
 
-        field2 = MyBoolean(error_messages={"invalid": "bad input"})
+        field3 = MyBoolean(error_messages={"invalid": "bad input"})
         with pytest.raises(ValidationError) as excinfo:
-            field2.deserialize(in_val)
+            field3.deserialize(in_val)
         assert str(excinfo.value.args[0]) == "bad input"
 
-        field2 = fields.Boolean(
-            truthy=("yep",), error_messages={"invalid": "bad input"}
-        )
-
     def test_boolean_field_deserialization_with_empty_truthy(self):
-        field = fields.Boolean(truthy=())
+        field = fields.Boolean(truthy=set())
         assert field.deserialize("yep") is True
         assert field.deserialize(True) is True
         assert field.deserialize(False) is False
@@ -1193,16 +1190,16 @@ class TestFieldDeserialization:
     def test_enum_field_by_value_true_deserialization(self):
         field = fields.Enum(HairColorEnum, by_value=True)
         assert field.deserialize("black hair") == HairColorEnum.black
-        field = fields.Enum(GenderEnum, by_value=True)
-        assert field.deserialize(1) == GenderEnum.male
+        field2 = fields.Enum(GenderEnum, by_value=True)
+        assert field2.deserialize(1) == GenderEnum.male
 
     def test_enum_field_by_value_field_deserialization(self):
         field = fields.Enum(HairColorEnum, by_value=fields.String)
         assert field.deserialize("black hair") == HairColorEnum.black
-        field = fields.Enum(GenderEnum, by_value=fields.Integer)
-        assert field.deserialize(1) == GenderEnum.male
-        field = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y"))
-        assert field.deserialize("29/02/2004") == DateEnum.date_1
+        field2 = fields.Enum(GenderEnum, by_value=fields.Integer)
+        assert field2.deserialize(1) == GenderEnum.male
+        field3 = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y"))
+        assert field3.deserialize("29/02/2004") == DateEnum.date_1
 
     def test_enum_field_by_value_true_invalid_value(self):
         field = fields.Enum(HairColorEnum, by_value=True)
@@ -1211,9 +1208,9 @@ class TestFieldDeserialization:
             match="Must be one of: black hair, brown hair, blond hair, red hair.",
         ):
             field.deserialize("dummy")
-        field = fields.Enum(GenderEnum, by_value=True)
+        field2 = fields.Enum(GenderEnum, by_value=True)
         with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."):
-            field.deserialize(12)
+            field2.deserialize(12)
 
     def test_enum_field_by_value_field_invalid_value(self):
         field = fields.Enum(HairColorEnum, by_value=fields.String)
@@ -1222,14 +1219,14 @@ class TestFieldDeserialization:
             match="Must be one of: black hair, brown hair, blond hair, red hair.",
         ):
             field.deserialize("dummy")
-        field = fields.Enum(GenderEnum, by_value=fields.Integer)
+        field2 = fields.Enum(GenderEnum, by_value=fields.Integer)
         with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."):
-            field.deserialize(12)
-        field = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y"))
+            field2.deserialize(12)
+        field3 = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y"))
         with pytest.raises(
             ValidationError, match="Must be one of: 29/02/2004, 29/02/2008, 29/02/2012."
         ):
-            field.deserialize("28/02/2004")
+            field3.deserialize("28/02/2004")
 
     def test_enum_field_by_value_true_wrong_type(self):
         field = fields.Enum(HairColorEnum, by_value=True)
@@ -1879,16 +1876,14 @@ class TestSchemaDeserialization:
             foo = fields.Raw(required=True)
             bar = fields.Raw(required=True)
 
-        schema_args = {}
-        load_args = {}
+        data = {"foo": 3}
         if partial_schema:
-            schema_args["partial"] = True
+            result = MySchema(partial=True).load(data)
         else:
-            load_args["partial"] = True
-        data = MySchema(**schema_args).load({"foo": 3}, **load_args)
+            result = MySchema().load(data, partial=True)
 
-        assert data["foo"] == 3
-        assert "bar" not in data
+        assert result["foo"] == 3
+        assert "bar" not in result
 
     def test_partial_fields_deserialization(self):
         class MySchema(Schema):
@@ -1904,11 +1899,13 @@ class TestSchemaDeserialization:
         assert "baz" in errors
 
         data = MySchema().load({"foo": 3}, partial=("bar", "baz"))
+        assert isinstance(data, dict)
         assert data["foo"] == 3
         assert "bar" not in data
         assert "baz" not in data
 
         data = MySchema(partial=True).load({"foo": 3}, partial=("bar", "baz"))
+        assert isinstance(data, dict)
         assert data["foo"] == 3
         assert "bar" not in data
         assert "baz" not in data
@@ -2319,4 +2316,5 @@ def test_deserialize_raises_exception_if_input_type_is_incorrect(data, unknown):
     with pytest.raises(ValidationError, match="Invalid input type.") as excinfo:
         MySchema(unknown=unknown).load(data)
     exc = excinfo.value
+    assert isinstance(exc.messages, dict)
     assert list(exc.messages.keys()) == ["_schema"]
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 898abe49..a6ddf7e4 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -43,7 +43,7 @@ class TestField:
 
     def test_error_raised_if_uncallable_validator_passed(self):
         with pytest.raises(ValueError, match="must be a callable"):
-            fields.Raw(validate="notcallable")
+            fields.Raw(validate="notcallable")  # type: ignore[arg-type]
 
     def test_error_raised_if_missing_is_set_on_required_field(self):
         with pytest.raises(
@@ -53,10 +53,10 @@ class TestField:
 
     def test_custom_field_receives_attr_and_obj(self):
         class MyField(fields.Field[str]):
-            def _deserialize(self, val, attr, data, **kwargs):
+            def _deserialize(self, value, attr, data, **kwargs) -> str:
                 assert attr == "name"
                 assert data["foo"] == 42
-                return val
+                return str(value)
 
         class MySchema(Schema):
             name = MyField()
@@ -66,10 +66,10 @@ class TestField:
 
     def test_custom_field_receives_data_key_if_set(self):
         class MyField(fields.Field[str]):
-            def _deserialize(self, val, attr, data, **kwargs):
+            def _deserialize(self, value, attr, data, **kwargs):
                 assert attr == "name"
                 assert data["foo"] == 42
-                return val
+                return str(value)
 
         class MySchema(Schema):
             Name = MyField(data_key="name")
@@ -79,10 +79,10 @@ class TestField:
 
     def test_custom_field_follows_data_key_if_set(self):
         class MyField(fields.Field[str]):
-            def _serialize(self, val, attr, data):
+            def _serialize(self, value, attr, obj, **kwargs) -> str:
                 assert attr == "name"
-                assert data["foo"] == 42
-                return val
+                assert obj["foo"] == 42
+                return str(value)
 
         class MySchema(Schema):
             name = MyField(data_key="_NaMe")
@@ -95,7 +95,7 @@ class TestParentAndName:
     class MySchema(Schema):
         foo = fields.Raw()
         bar = fields.List(fields.Str())
-        baz = fields.Tuple([fields.Str(), fields.Int()])
+        baz = fields.Tuple((fields.Str(), fields.Int()))
         bax = fields.Dict(fields.Str(), fields.Int())
 
     @pytest.fixture()
@@ -150,8 +150,14 @@ class TestParentAndName:
             pass
 
         schema2 = OtherSchema()
-        assert schema.fields["bar"].inner.root == schema
-        assert schema2.fields["bar"].inner.root == schema2
+
+        bar_field = schema.fields["bar"]
+        assert isinstance(bar_field, fields.List)
+        assert bar_field.inner.root == schema
+
+        bar_field2 = schema2.fields["bar"]
+        assert isinstance(bar_field2, fields.List)
+        assert bar_field2.inner.root == schema2
 
     def test_dict_root_inheritance(self):
         class MySchema(Schema):
@@ -162,10 +168,20 @@ class TestParentAndName:
 
         schema = MySchema()
         schema2 = OtherSchema()
-        assert schema.fields["foo"].key_field.root == schema
-        assert schema.fields["foo"].value_field.root == schema
-        assert schema2.fields["foo"].key_field.root == schema2
-        assert schema2.fields["foo"].value_field.root == schema2
+
+        foo_field = schema.fields["foo"]
+        assert isinstance(foo_field, fields.Dict)
+        assert isinstance(foo_field.key_field, fields.Str)
+        assert isinstance(foo_field.value_field, fields.Int)
+        assert foo_field.key_field.root == schema
+        assert foo_field.value_field.root == schema
+
+        foo_field2 = schema2.fields["foo"]
+        assert isinstance(foo_field2, fields.Dict)
+        assert isinstance(foo_field2.key_field, fields.Str)
+        assert isinstance(foo_field2.value_field, fields.Int)
+        assert foo_field2.key_field.root == schema2
+        assert foo_field2.value_field.root == schema2
 
     # Regression test for https://github.com/marshmallow-code/marshmallow/issues/1357
     def test_datetime_list_inner_format(self, schema):
@@ -246,7 +262,7 @@ class TestNestedField:
     @pytest.mark.parametrize("param", ("only", "exclude"))
     def test_nested_only_and_exclude_as_string(self, param):
         with pytest.raises(StringNotCollectionError):
-            fields.Nested(Schema, **{param: "foo"})
+            fields.Nested(Schema, **{param: "foo"})  # type: ignore[arg-type]
 
     @pytest.mark.parametrize(
         "nested_value",
@@ -320,8 +336,11 @@ class TestListNested:
         class Family(Schema):
             children = fields.List(fields.Nested(Child))
 
-        schema = Family(**{param: ["children.name"]})
-        assert getattr(schema.fields["children"].inner.schema, param) == {"name"}
+        schema = Family(**{param: ["children.name"]})  # type: ignore[arg-type]
+        children_field = schema.fields["children"]
+        assert isinstance(children_field, fields.List)
+        assert isinstance(children_field.inner, fields.Nested)
+        assert getattr(children_field.inner.schema, param) == {"name"}
 
     @pytest.mark.parametrize(
         ("param", "expected_attribute", "expected_dump"),
@@ -339,10 +358,12 @@ class TestListNested:
             age = fields.Integer()
 
         class Family(Schema):
-            children = fields.List(fields.Nested(Child, **{param: ("name", "surname")}))
+            children = fields.List(fields.Nested(Child, **{param: ("name", "surname")}))  # type: ignore[arg-type]
 
-        schema = Family(**{param: ["children.name", "children.age"]})
-        assert getattr(schema.fields["children"].inner, param) == expected_attribute
+        schema = Family(**{param: ["children.name", "children.age"]})  # type: ignore[arg-type]
+        children_field = schema.fields["children"]
+        assert isinstance(children_field, fields.List)
+        assert getattr(children_field.inner, param) == expected_attribute
 
         family = {"children": [{"name": "Lily", "surname": "Martinez", "age": 15}]}
         assert schema.dump(family) == expected_dump
@@ -380,12 +401,13 @@ class TestListNested:
             age = fields.Integer()
 
         class Family(Schema):
-            children = fields.List(fields.Nested(Child(**{param: ("name", "surname")})))
+            children = fields.List(fields.Nested(Child(**{param: ("name", "surname")})))  # type: ignore[arg-type]
 
-        schema = Family(**{param: ["children.name", "children.age"]})
-        assert (
-            getattr(schema.fields["children"].inner.schema, param) == expected_attribute
-        )
+        schema = Family(**{param: ["children.name", "children.age"]})  # type: ignore[arg-type]
+        children_field = schema.fields["children"]
+        assert isinstance(children_field, fields.List)
+        assert isinstance(children_field.inner, fields.Nested)
+        assert getattr(children_field.inner.schema, param) == expected_attribute
 
         family = {"children": [{"name": "Lily", "surname": "Martinez", "age": 15}]}
         assert schema.dump(family) == expected_dump
@@ -424,13 +446,14 @@ class TestListNested:
 
         class Family(Schema):
             children = fields.List(
-                fields.Nested(lambda: Child(**{param: ("name", "surname")}))
+                fields.Nested(lambda: Child(**{param: ("name", "surname")}))  # type: ignore[arg-type]
             )
 
-        schema = Family(**{param: ["children.name", "children.age"]})
-        assert (
-            getattr(schema.fields["children"].inner.schema, param) == expected_attribute
-        )
+        schema = Family(**{param: ["children.name", "children.age"]})  # type: ignore[arg-type]
+        children_field = schema.fields["children"]
+        assert isinstance(children_field, fields.List)
+        assert isinstance(children_field.inner, fields.Nested)
+        assert getattr(children_field.inner.schema, param) == expected_attribute
 
         family = {"children": [{"name": "Lily", "surname": "Martinez", "age": 15}]}
         assert schema.dump(family) == expected_dump
@@ -476,13 +499,14 @@ class TestTupleNested:
         class Family(Schema):
             children = fields.Tuple((fields.Nested(Child), fields.Nested(Child)))
 
-        schema = Family(**{param: ["children.name"]})
-        assert getattr(schema.fields["children"].tuple_fields[0].schema, param) == {
-            "name"
-        }
-        assert getattr(schema.fields["children"].tuple_fields[1].schema, param) == {
-            "name"
-        }
+        schema = Family(**{param: ["children.name"]})  # type: ignore[arg-type]
+        children_field = schema.fields["children"]
+        assert isinstance(children_field, fields.Tuple)
+        field1, field2 = children_field.tuple_fields
+        assert isinstance(field1, fields.Nested)
+        assert isinstance(field2, fields.Nested)
+        assert getattr(field1.schema, param) == {"name"}
+        assert getattr(field2.schema, param) == {"name"}
 
     def test_tuple_nested_partial_propagated_to_nested(self):
         class Child(Schema):
@@ -525,8 +549,11 @@ class TestDictNested:
         class Family(Schema):
             children = fields.Dict(values=fields.Nested(Child))
 
-        schema = Family(**{param: ["children.name"]})
-        assert getattr(schema.fields["children"].value_field.schema, param) == {"name"}
+        schema = Family(**{param: ["children.name"]})  # type: ignore[arg-type]
+        children_field = schema.fields["children"]
+        assert isinstance(children_field, fields.Dict)
+        assert isinstance(children_field.value_field, fields.Nested)
+        assert getattr(children_field.value_field.schema, param) == {"name"}
 
     @pytest.mark.parametrize(
         ("param", "expected"),
@@ -540,11 +567,13 @@ class TestDictNested:
 
         class Family(Schema):
             children = fields.Dict(
-                values=fields.Nested(Child, **{param: ("name", "surname")})
+                values=fields.Nested(Child, **{param: ("name", "surname")})  # type: ignore[arg-type]
             )
 
-        schema = Family(**{param: ["children.name", "children.age"]})
-        assert getattr(schema.fields["children"].value_field, param) == expected
+        schema = Family(**{param: ["children.name", "children.age"]})  # type: ignore[arg-type]
+        children_field = schema.fields["children"]
+        assert isinstance(children_field, fields.Dict)
+        assert getattr(children_field.value_field, param) == expected
 
     def test_dict_nested_partial_propagated_to_nested(self):
         class Child(Schema):
diff --git a/tests/test_options.py b/tests/test_options.py
index 3df0660b..dfc3e94f 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -140,7 +140,7 @@ class TestIncludeOption:
         assert list(result.keys()) == expected_fields
 
     def test_added_fields_are_inherited(self):
-        class AddFieldsChild(self.AddFieldsSchema):
+        class AddFieldsChild(self.AddFieldsSchema):  # type: ignore[name-defined]
             email = fields.Str()
 
         s = AddFieldsChild()
diff --git a/tests/test_registry.py b/tests/test_registry.py
index 11905549..e4525b9d 100644
--- a/tests/test_registry.py
+++ b/tests/test_registry.py
@@ -57,7 +57,9 @@ def test_serializer_class_registry_register_same_classname_different_module():
     type("MyTestRegSchema", (Schema,), {"__module__": "modA"})
 
     assert "MyTestRegSchema" in class_registry._registry
-    assert len(class_registry._registry.get("MyTestRegSchema")) == 1
+    result = class_registry._registry.get("MyTestRegSchema")
+    assert isinstance(result, list)
+    assert len(result) == 1
     assert "modA.MyTestRegSchema" in class_registry._registry
     #  storing for classname and fullpath
     assert len(class_registry._registry) == reglen + 2
@@ -66,7 +68,9 @@ def test_serializer_class_registry_register_same_classname_different_module():
 
     assert "MyTestRegSchema" in class_registry._registry
     #  aggregating classes with same name from different modules
-    assert len(class_registry._registry.get("MyTestRegSchema")) == 2
+    result = class_registry._registry.get("MyTestRegSchema")
+    assert isinstance(result, list)
+    assert len(result) == 2
     assert "modB.MyTestRegSchema" in class_registry._registry
     #  storing for same classname (+0) and different module (+1)
     assert len(class_registry._registry) == reglen + 2 + 1
@@ -75,7 +79,9 @@ def test_serializer_class_registry_register_same_classname_different_module():
 
     assert "MyTestRegSchema" in class_registry._registry
     #  only the class with matching module has been replaced
-    assert len(class_registry._registry.get("MyTestRegSchema")) == 2
+    result = class_registry._registry.get("MyTestRegSchema")
+    assert isinstance(result, list)
+    assert len(result) == 2
     assert "modB.MyTestRegSchema" in class_registry._registry
     #  only the class with matching module has been replaced (+0)
     assert len(class_registry._registry) == reglen + 2 + 1
@@ -87,9 +93,13 @@ def test_serializer_class_registry_override_if_same_classname_same_module():
     type("MyTestReg2Schema", (Schema,), {"__module__": "SameModulePath"})
 
     assert "MyTestReg2Schema" in class_registry._registry
-    assert len(class_registry._registry.get("MyTestReg2Schema")) == 1
+    result = class_registry._registry.get("MyTestReg2Schema")
+    assert isinstance(result, list)
+    assert len(result) == 1
     assert "SameModulePath.MyTestReg2Schema" in class_registry._registry
-    assert len(class_registry._registry.get("SameModulePath.MyTestReg2Schema")) == 1
+    result = class_registry._registry.get("SameModulePath.MyTestReg2Schema")
+    assert isinstance(result, list)
+    assert len(result) == 1
     #  storing for classname and fullpath
     assert len(class_registry._registry) == reglen + 2
 
@@ -97,10 +107,15 @@ def test_serializer_class_registry_override_if_same_classname_same_module():
 
     assert "MyTestReg2Schema" in class_registry._registry
     #  overriding same class name and same module
-    assert len(class_registry._registry.get("MyTestReg2Schema")) == 1
+    result = class_registry._registry.get("MyTestReg2Schema")
+    assert isinstance(result, list)
+    assert len(result) == 1
+
     assert "SameModulePath.MyTestReg2Schema" in class_registry._registry
     #  overriding same fullpath
-    assert len(class_registry._registry.get("SameModulePath.MyTestReg2Schema")) == 1
+    result = class_registry._registry.get("SameModulePath.MyTestReg2Schema")
+    assert isinstance(result, list)
+    assert len(result) == 1
     #  overriding for same classname (+0) and different module (+0)
     assert len(class_registry._registry) == reglen + 2
 
@@ -212,5 +227,5 @@ def test_can_use_full_module_path_to_class():
     class Schema2(Schema):
         foo = fields.Nested("tests.test_registry.FooSerializer")
 
-    sch = Schema2()
-    assert sch.dump({"foo": {"_id": 42}})
+    sch2 = Schema2()
+    assert sch2.dump({"foo": {"_id": 42}})
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 3d798a05..f5378ce0 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -90,6 +90,7 @@ def test_load_validation_error_stores_input_data_and_valid_data():
     except ValidationError as err:
         # err.data is the raw input data
         assert err.data == input_data
+        assert isinstance(err.valid_data, dict)
         assert "always_valid" in err.valid_data
         # err.valid_data contains valid, deserialized data
         assert isinstance(err.valid_data["always_valid"], dt.datetime)
@@ -108,6 +109,7 @@ def test_load_resets_error_fields():
     with pytest.raises(ValidationError) as excinfo:
         schema.load({"name": "Joe", "email": "not-valid"})
     exc = excinfo.value
+    assert isinstance(exc.messages, dict)
     assert len(exc.messages.keys()) == 1
 
     with pytest.raises(ValidationError) as excinfo:
@@ -118,7 +120,7 @@ def test_load_resets_error_fields():
 def test_errored_fields_do_not_appear_in_output():
     class MyField(fields.Field[int]):
         # Make sure validation fails during serialization
-        def _serialize(self, val, attr, obj):
+        def _serialize(self, value, attr, obj, **kwargs):
             raise ValidationError("oops")
 
     def validator(val):
@@ -133,6 +135,7 @@ def test_errored_fields_do_not_appear_in_output():
     data, errors = excinfo.value.valid_data, excinfo.value.messages
 
     assert "foo" in errors
+    assert isinstance(data, dict)
     assert "foo" not in data
 
 
@@ -356,7 +359,9 @@ def test_nested_on_bind_field_hook():
         foo = fields.Nested(NestedSchema)
 
     schema = MySchema()
-    assert schema.fields["foo"].schema.fields["bar"].metadata["fname"] == "bar"
+    foo_field = schema.fields["foo"]
+    assert isinstance(foo_field, fields.Nested)
+    assert foo_field.schema.fields["bar"].metadata["fname"] == "bar"
 
 
 class TestValidate:
@@ -661,7 +666,7 @@ def test_custom_type_error_message():
     s = ErrorSchema()
     u = ["Joe"]
     with pytest.raises(ValidationError) as excinfo:
-        s.load(u)
+        s.load(u)  # type: ignore[arg-type]
     errors = excinfo.value.messages
     assert custom_message in errors["_schema"]
 
@@ -697,14 +702,13 @@ def test_custom_error_messages_with_inheritance():
         error_messages = {"type": child_type_message}
 
     unknown_user = {"name": "Eleven", "age": 12}
-    type_user = 11
 
     parent_schema = ParentSchema()
     with pytest.raises(ValidationError) as excinfo:
         parent_schema.load(unknown_user)
     assert parent_unknown_message in excinfo.value.messages["age"]
     with pytest.raises(ValidationError) as excinfo:
-        parent_schema.load(type_user)
+        parent_schema.load(11)  # type: ignore[arg-type]
     assert parent_type_message in excinfo.value.messages["_schema"]
 
     child_schema = ChildSchema()
@@ -712,7 +716,7 @@ def test_custom_error_messages_with_inheritance():
         child_schema.load(unknown_user)
     assert parent_unknown_message in excinfo.value.messages["age"]
     with pytest.raises(ValidationError) as excinfo:
-        child_schema.load(type_user)
+        child_schema.load(11)  # type: ignore[arg-type]
     assert child_type_message in excinfo.value.messages["_schema"]
 
 
@@ -1487,7 +1491,7 @@ def test_only_and_exclude_as_string(param):
         foo = fields.Raw()
 
     with pytest.raises(StringNotCollectionError):
-        MySchema(**{param: "foo"})
+        MySchema(**{param: "foo"})  # type: ignore[arg-type]
 
 
 def test_nested_with_sets():
@@ -1605,7 +1609,7 @@ class MySchema(Schema):
     email = fields.Email()
     age = fields.Integer()
 
-    def handle_error(self, errors, obj, **kwargs):
+    def handle_error(self, error, data, *args, **kwargs):
         raise CustomError("Something bad happened")
 
     def test_load_with_custom_error_handler(self):
@@ -1617,6 +1621,7 @@ class MySchema(Schema):
             def handle_error(self, error, data, **kwargs):
                 assert type(error) is ValidationError
                 assert "email" in error.messages
+                assert isinstance(error.messages, dict)
                 assert list(error.messages.keys()) == ["email"]
                 assert data == in_data
                 raise CustomError("Something bad happened")
@@ -1634,6 +1639,7 @@ class MySchema(Schema):
             def handle_error(self, error, data, **kwargs):
                 assert type(error) is ValidationError
                 assert "email" in error.messages
+                assert isinstance(error.messages, dict)
                 assert list(error.messages.keys()) == ["email"]
                 assert data == in_data
                 raise CustomError("Something bad happened")
@@ -1655,6 +1661,7 @@ class MySchema(Schema):
             def handle_error(self, error, data, **kwargs):
                 assert type(error) is ValidationError
                 assert "num" in error.messages
+                assert isinstance(error.messages, dict)
                 assert list(error.messages.keys()) == ["num"]
                 assert data == in_data
                 raise CustomError("Something bad happened")
@@ -1674,6 +1681,7 @@ class MySchema(Schema):
 
             def handle_error(self, error, data, **kwargs):
                 assert type(error) is ValidationError
+                assert isinstance(error.messages, dict)
                 assert list(error.messages.keys()) == ["_schema"]
                 assert data == in_data
                 raise CustomError("Something bad happened")
@@ -1888,7 +1896,7 @@ class TestNestedSchema:
 
     def test_nested_fields_must_be_passed_a_serializer(self, blog):
         class BadNestedFieldSchema(BlogSchema):
-            user = fields.Nested(fields.String)
+            user = fields.Nested(fields.String)  # type: ignore[arg-type]
 
         with pytest.raises(ValueError):
             BadNestedFieldSchema().dump(blog)
@@ -1949,9 +1957,10 @@ class TestNestedSchema:
 
         data = {"child": {"num": 1, "extra": 1}}
         if unknown is None or unknown == RAISE:
-            with pytest.raises(ValidationError) as exc:
+            with pytest.raises(ValidationError) as excinfo:
                 ParentSchema().load(data)
-                assert exc.messages == {"child": {"extra": ["Unknown field."]}}
+            exc = excinfo.value
+            assert exc.messages == {"child": {"extra": ["Unknown field."]}}
         else:
             output = {
                 INCLUDE: {"child": {"num": 1, "extra": 1}},
@@ -2119,12 +2128,12 @@ def test_serialization_with_required_field():
 
 
 def test_deserialization_with_required_field():
-    in_data = {}
     with pytest.raises(ValidationError) as excinfo:
-        RequiredUserSchema().load(in_data)
+        RequiredUserSchema().load({})
     data, errors = excinfo.value.valid_data, excinfo.value.messages
     assert "name" in errors
     assert "Missing data for required field." in errors["name"]
+    assert isinstance(data, dict)
     # field value should also not be in output data
     assert "name" not in data
 
diff --git a/tests/test_serialization.py b/tests/test_serialization.py
index 7c30520d..5c20c71c 100644
--- a/tests/test_serialization.py
+++ b/tests/test_serialization.py
@@ -87,7 +87,7 @@ class TestFieldSerialization:
 
     def test_function_field_passed_uncallable_object(self):
         with pytest.raises(TypeError):
-            fields.Function("uncallable")
+            fields.Function("uncallable")  # type: ignore[arg-type]
 
     def test_integer_field(self, user):
         field = fields.Integer()
@@ -232,8 +232,8 @@ class TestFieldSerialization:
         field = fields.Enum(HairColorEnum, by_value=True)
         assert field.serialize("hair_color", user) == "black hair"
         user.sex = GenderEnum.male
-        field = fields.Enum(GenderEnum, by_value=True)
-        assert field.serialize("sex", user) == 1
+        field2 = fields.Enum(GenderEnum, by_value=True)
+        assert field2.serialize("sex", user) == 1
         user.some_date = DateEnum.date_1
 
     def test_enum_field_by_value_field_serialization(self, user):
@@ -241,11 +241,11 @@ class TestFieldSerialization:
         field = fields.Enum(HairColorEnum, by_value=fields.String)
         assert field.serialize("hair_color", user) == "black hair"
         user.sex = GenderEnum.male
-        field = fields.Enum(GenderEnum, by_value=fields.Integer)
-        assert field.serialize("sex", user) == 1
+        field2 = fields.Enum(GenderEnum, by_value=fields.Integer)
+        assert field2.serialize("sex", user) == 1
         user.some_date = DateEnum.date_1
-        field = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y"))
-        assert field.serialize("some_date", user) == "29/02/2004"
+        field3 = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y"))
+        assert field3.serialize("some_date", user) == "29/02/2004"
 
     def test_decimal_field(self, user):
         user.m1 = 12
@@ -483,7 +483,7 @@ class TestFieldSerialization:
         m = fields.Method()
         m.parent = Schema()
 
-        assert m.serialize("", "", "") is missing_
+        assert m.serialize("", "", None) is missing_
 
     def test_serialize_with_data_key_param(self):
         class DumpToSchema(Schema):
@@ -851,13 +851,13 @@ class TestFieldSerialization:
             id = fields.Int()
 
         with pytest.raises(ValueError):
-            fields.List("string")
+            fields.List("string")  # type: ignore[arg-type]
         expected_msg = (
             "The list elements must be a subclass or instance of "
             "marshmallow.fields.Field"
         )
         with pytest.raises(ValueError, match=expected_msg):
-            fields.List(ASchema)
+            fields.List(ASchema)  # type: ignore[arg-type]
 
     def test_datetime_integer_tuple_field(self):
         obj = DateTimeIntegerTuple((dt.datetime.now(dt.timezone.utc), 42))
@@ -876,15 +876,15 @@ class TestFieldSerialization:
             id = fields.Int()
 
         with pytest.raises(ValueError):
-            fields.Tuple(["string"])
+            fields.Tuple(["string"])  # type: ignore[arg-type]
         with pytest.raises(ValueError):
-            fields.Tuple(fields.String)
+            fields.Tuple(fields.String)  # type: ignore[arg-type]
         expected_msg = (
             'Elements of "tuple_fields" must be subclasses or '
             "instances of marshmallow.fields.Field."
         )
         with pytest.raises(ValueError, match=expected_msg):
-            fields.Tuple([ASchema])
+            fields.Tuple([ASchema])  # type: ignore[arg-type]
 
     def test_serialize_does_not_apply_validators(self, user):
         field = fields.Raw(validate=lambda x: False)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 84b81290..1caa3f7c 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -77,7 +77,7 @@ def test_get_value():
 
 
 def test_set_value():
-    d = {}
+    d: dict[str, int | dict] = {}
     utils.set_value(d, "foo", 42)
     assert d == {"foo": 42}