Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking UUID in Python dataclasses

I have dataclasses with uuids like this:

import uuid

from dataclasses import dataclass, field
from typing import Union

@dataclass
class Foo:
    id: Union[uuid.UUID, None] = field(default_factory=uuid.uuid4)

When I call Foo() it creates an object with a generated UUID - fine.

Now I want to mock this UUID factory in a test, like this:

from unittest.mock import patch

TEST_UUIDS = ["uuid1", "uuid2"]

with patch.object(uuid, "uuid4", side_effects=TEST_UUIDS):
    print(uuid.uuid4())  # Output: uuid1
    print(Foo().id)  # Output: an actual UUID

My expected output is uuid2. So the question: How do I have to patch the factory correctly? Couldn't find anything in the docs or here...

like image 371
raphaelj Avatar asked Jun 04 '26 14:06

raphaelj


1 Answers

TL;DR It is probably best to just do print(Foo(id="uuid1").id)

Explanation

Here is what does not work: (apart from the typo at side_effect(s))

with patch.object(uuid, "uuid4", side_effect=TEST_UUIDS):
    print(id(uuid.uuid4))
    print(id(Foo.__dataclass_fields__["id"].default_factory))

Outputs:

140470453723216
140470456506848

This is because patch is not changing the actual uuid.uuid4 function, but creating a new one, overwriting the normal uuid.uuid4 in the namespace, as seen by the change in id. Since the factory is already initialized, this change has no effect, unless you create the class inside the with block, which is impractical for a real life test.

But even if it did, that would not make a difference, because dataclass generates the __init__ function, and creates the reference to the default factory exactly once at during the class definition, and not for every single __init__ call. Therefore, the following also does not work:

with patch.object(Foo.__dataclass_fields__["id"], "default_factory",
                  side_effect=TEST_UUIDS):
    print(Foo().id)

What you can do is overriding the __init__ function:

from copy import copy
#  Copy is required to avoid recursion after rewrite
foo_init_copy = copy(Foo.__init__)
def mock_init(foo):
    foo_init_copy(foo)
    foo.id = "x"

with patch.object(Foo, "__init__", new=mock_init):
   ...

Which seems a like a little overkill to me, if the factory normally does not implement complicated logic. If that is the case, one can also consider creating a normal class instead of a data class.

like image 69
Ali Rasim Kocal Avatar answered Jun 08 '26 00:06

Ali Rasim Kocal