Skip to content

When using pytest-xdist, the RedundantVerifyWarning constructor throws an exception because NamedTuple is deserialized as str #271

@zlalvani

Description

@zlalvani

Thanks @mcous for your awesome library! It took some time to find a mocking framework in python that had good support for type checking so I really appreciate it.

When parallelizing my tests with pytest-xdist I discovered that the RedundantVerifyWarning constructor throws an exception when unpacking a tuple:

INTERNALERROR>   File "/Users/me/repo/.venv/lib/python3.13/site-packages/decoy/stringify.py", line 21, in stringify_call
INTERNALERROR>     spy, payload = event
INTERNALERROR>     ^^^^^^^^^^^^
INTERNALERROR> ValueError: too many values to unpack (expected 2)
[gw10] node down: <ExceptionInfo ValueError('too many values to unpack (expected 2)') tblen=4>

This occurs because in pytest-xdist serializes warnings including their message, and then deserializes them.

I've made a simple fix below, but it's possible you want to address this by serializing and deserializing the rich metadata more explicitly.

class RedundantVerifyWarning(DecoyWarning):
    """A warning when a mock is redundantly checked with `verify`.

    A `verify` assertion is redundant if:

    - A given call is used as a [`when`][decoy.Decoy.when] rehearsal
    - That same call is later used in a [`verify`][decoy.Decoy.verify] check

    See the [RedundantVerifyWarning guide][] for more details.

    [RedundantVerifyWarning guide]: usage/errors-and-warnings.md#redundantverifywarning
    """

    def __init__(self, rehearsal: VerifyRehearsal | str) -> None:
        message = (
            os.linesep.join(
                [
                    "The same rehearsal was used in both a `when` and a `verify`.",
                    "This is redundant and probably a misuse of the mock.",
                    f"\t{stringify_call(rehearsal)}",
                    "See https://michael.cousins.io/decoy/usage/errors-and-warnings/#redundantverifywarning",
                ]
            )
            if isinstance(rehearsal, SpyRehearsal)
            else rehearsal
        )
        super().__init__(message)
        self.rehearsal = rehearsal

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions