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...
TL;DR It is probably best to just do print(Foo(id="uuid1").id)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With