Skip to content

Commit

Permalink
More on Tortoise factories
Browse files Browse the repository at this point in the history
  • Loading branch information
barseghyanartur committed Dec 3, 2023
1 parent 7e47cf4 commit dd89c21
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 36 deletions.
35 changes: 35 additions & 0 deletions examples/django/article/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,38 @@ def test_sub_factory(self) -> None:
articles = ArticleFactory.create_batch(5)
self.assertEqual(len(articles), 5)
self.assertIsInstance(articles[0], Article)

def test_sub_factory_nested(self) -> None:
article = ArticleFactory(author__username="admin")

# Testing SubFactory
self.assertIsInstance(article.author, User)
self.assertIsInstance(article.author.id, int)
self.assertIsInstance(article.author.is_staff, bool)
self.assertIsInstance(article.author.date_joined, datetime)
self.assertEqual(article.author.username, "admin")

# Testing Factory
self.assertIsInstance(article.id, int)
self.assertIsInstance(article.slug, str)

# Testing hooks
self.assertTrue(
hasattr(article, "pre_save_called") and article.pre_save_called
)
self.assertTrue(
hasattr(article, "post_save_called") and article.post_save_called
)
self.assertTrue(
hasattr(article.author, "pre_save_called")
and article.author.pre_save_called
)
self.assertTrue(
hasattr(article.author, "post_save_called")
and article.author.post_save_called
)

# Testing batch creation
articles = ArticleFactory.create_batch(5)
self.assertEqual(len(articles), 5)
self.assertIsInstance(articles[0], Article)
102 changes: 70 additions & 32 deletions examples/tortoise/article/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,73 @@ def tearDown(self):
FILE_REGISTRY.clean_up()

def test_sub_factory(self) -> None:
article = ArticleFactory()

# Testing SubFactory
self.assertIsInstance(article.author, User)
self.assertIsInstance(article.author.id, int)
self.assertIsInstance(article.author.is_staff, bool)
self.assertIsInstance(article.author.date_joined, datetime)

# Testing Factory
self.assertIsInstance(article.id, int)
self.assertIsInstance(article.slug, str)

# Testing hooks
self.assertTrue(
hasattr(article, "pre_save_called") and article.pre_save_called
)
self.assertTrue(
hasattr(article, "post_save_called") and article.post_save_called
)
self.assertTrue(
hasattr(article.author, "pre_save_called")
and article.author.pre_save_called
)
self.assertTrue(
hasattr(article.author, "post_save_called")
and article.author.post_save_called
)

# Testing batch creation
articles = ArticleFactory.create_batch(5)
self.assertEqual(len(articles), 5)
self.assertIsInstance(articles[0], Article)
with self.subTest("SubFactory"):
article = ArticleFactory()

# Testing SubFactory
self.assertIsInstance(article.author, User)
self.assertIsInstance(article.author.id, int)
self.assertIsInstance(article.author.is_staff, bool)
self.assertIsInstance(article.author.date_joined, datetime)

# Testing Factory
self.assertIsInstance(article.id, int)
self.assertIsInstance(article.slug, str)

# Testing hooks
self.assertTrue(
hasattr(article, "pre_save_called") and article.pre_save_called
)
self.assertTrue(
hasattr(article, "post_save_called")
and article.post_save_called
)
self.assertTrue(
hasattr(article.author, "pre_save_called")
and article.author.pre_save_called
)
self.assertTrue(
hasattr(article.author, "post_save_called")
and article.author.post_save_called
)

# Testing batch creation
articles = ArticleFactory.create_batch(5)
self.assertEqual(len(articles), 5)
self.assertIsInstance(articles[0], Article)

with self.subTest("SubFactory nested params"):
article = ArticleFactory(author__username="admin")

# Testing SubFactory
self.assertIsInstance(article.author, User)
self.assertIsInstance(article.author.id, int)
self.assertIsInstance(article.author.is_staff, bool)
self.assertIsInstance(article.author.date_joined, datetime)
self.assertEqual(article.author.username, "admin")

# Testing Factory
self.assertIsInstance(article.id, int)
self.assertIsInstance(article.slug, str)

# Testing hooks
self.assertTrue(
hasattr(article, "pre_save_called") and article.pre_save_called
)
self.assertTrue(
hasattr(article, "post_save_called")
and article.post_save_called
)
self.assertTrue(
hasattr(article.author, "pre_save_called")
and article.author.pre_save_called
)
self.assertTrue(
hasattr(article.author, "post_save_called")
and article.author.post_save_called
)

# Testing batch creation
articles = ArticleFactory.create_batch(5)
self.assertEqual(len(articles), 5)
self.assertIsInstance(articles[0], Article)
148 changes: 144 additions & 4 deletions fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -1720,8 +1720,6 @@ def create(cls, **kwargs):
if instance:
return instance

# Create a new instance if none found
# return super().create(**kwargs)
model_data = {
field: value
for field, value in cls.__dict__.items()
Expand All @@ -1741,7 +1739,7 @@ def create(cls, **kwargs):
else direct_attrs[field]
)

# Create instance
# Create a new instance if none found
instance = cls.Meta.model(**model_data)

# Handle nested attributes
Expand Down Expand Up @@ -1806,8 +1804,61 @@ async def async_filter():
if instance:
return instance

model_data = {
field: value
for field, value in cls.__dict__.items()
if not field.startswith("_") and not field == "Meta"
}

# Separate nested attributes and direct attributes
nested_attrs = {k: v for k, v in kwargs.items() if "__" in k}
direct_attrs = {k: v for k, v in kwargs.items() if "__" not in k}

# Update direct attributes with callable results
for field, value in model_data.items():
if isinstance(value, (FactoryMethod, SubFactory)):
model_data[field] = (
value()
if field not in direct_attrs
else direct_attrs[field]
)

# Create a new instance if none found
return super().create(**kwargs)
instance = cls.Meta.model(**model_data)

# Handle nested attributes
for attr, value in nested_attrs.items():
field_name, nested_attr = attr.split("__", 1)
if isinstance(getattr(cls, field_name, None), SubFactory):

async def async_related_instance():
return await getattr(cls, field_name).factory_class.create(
**{nested_attr: value}
)

related_instance = asyncio.run(async_related_instance())
setattr(instance, field_name, related_instance)

# Run pre-save hooks
pre_save_hooks = [
method
for method in dir(cls)
if getattr(getattr(cls, method), "is_pre_save", False)
]
cls._run_hooks(pre_save_hooks, instance)

# Save instance
cls.save(instance)

# Run post-save hooks
post_save_hooks = [
method
for method in dir(cls)
if getattr(getattr(cls, method), "is_post_save", False)
]
cls._run_hooks(post_save_hooks, instance)

return instance


# TODO: Remove once Python 3.8 support is dropped
Expand Down Expand Up @@ -2519,6 +2570,10 @@ def __post_save_method(instance):
self.assertIsInstance(django_article.author.id, int)
self.assertIsInstance(django_article.author.is_staff, bool)
self.assertIsInstance(django_article.author.date_joined, datetime)
# Since we're mimicking Django's behaviour, the following line would
# fail on test, however would pass when testing against real Django
# model (as done in the examples).
# self.assertEqual(django_article.author.username, "admin")

# Testing Factory
self.assertIsInstance(django_article.id, int)
Expand All @@ -2539,6 +2594,91 @@ def __post_save_method(instance):
self.assertEqual(len(django_articles), 5)
self.assertIsInstance(django_articles[0], Article)

# **********************************
# ******* TortoiseModelFactory *******
# **********************************

class TortoiseUserFactory(TortoiseModelFactory):
id = FACTORY.pyint()
username = FACTORY.username()
first_name = FACTORY.first_name()
last_name = FACTORY.last_name()
email = FACTORY.email()
last_login = FACTORY.date_time()
is_superuser = False
is_staff = False
is_active = FACTORY.pybool()
date_joined = FACTORY.date_time()

class Meta:
model = User
get_or_create = ("username",)

@staticmethod
@pre_save
def __pre_save_method(instance):
instance.pre_save_called = True

@staticmethod
@post_save
def __post_save_method(instance):
instance.post_save_called = True

class TortoiseArticleFactory(TortoiseModelFactory):
id = FACTORY.pyint()
title = FACTORY.sentence()
slug = FACTORY.slug()
content = FACTORY.text()
image = FACTORY.png_file(storage=STORAGE)
pub_date = FACTORY.date()
safe_for_work = FACTORY.pybool()
minutes_to_read = FACTORY.pyint(min_value=1, max_value=10)
author = SubFactory(TortoiseUserFactory)

class Meta:
model = Article

@staticmethod
@pre_save
def __pre_save_method(instance):
instance.pre_save_called = True

@staticmethod
@post_save
def __post_save_method(instance):
instance.post_save_called = True

tortoise_article = TortoiseArticleFactory(author__username="admin")

# Testing SubFactory
self.assertIsInstance(tortoise_article.author, User)
self.assertIsInstance(tortoise_article.author.id, int)
self.assertIsInstance(tortoise_article.author.is_staff, bool)
self.assertIsInstance(tortoise_article.author.date_joined, datetime)
# Since we're mimicking Tortoise's behaviour, the following line would
# fail on test, however would pass when testing against real Tortoise
# model (as done in the examples).
# self.assertEqual(tortoise_article.author.username, "admin")

# Testing Factory
self.assertIsInstance(tortoise_article.id, int)
self.assertIsInstance(tortoise_article.slug, str)

# Testing hooks
self.assertTrue(
hasattr(tortoise_article, "pre_save_called")
and tortoise_article.pre_save_called
)
self.assertTrue(
hasattr(tortoise_article, "post_save_called")
and tortoise_article.post_save_called
)

# Testing batch creation
tortoise_articles = TortoiseArticleFactory.create_batch(5)
self.assertEqual(len(tortoise_articles), 5)
self.assertIsInstance(tortoise_articles[0], Article)

def test_registry_integration(self) -> None:
"""Test `add`."""
# Create a TXT file.
Expand Down

0 comments on commit dd89c21

Please sign in to comment.