question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Strategy to create related objects without creating main object

See original GitHub issue

The problem

I introduced Factory Boy to the team I work on and we love using it, but there is one scenario that comes up frequently for us where we find Factory Boy does not have a good solution.

When testing deserialization of an object that has one or more related objects associated with it, we often want to create all of the dependent objects and build (without creating) the object we want to deserialize. How I would typically deal with this situation is to use the dependent factory to create dependent object before using the other factory.

A typical test of a serializer might look something like this:

class AuthorFactory(factory.DjangoModelFactory):
    class Meta:
        model = Author

    first_name = factory.Faker("first_name")
    last_name = factory.Faker("last_name")


class BookFactory(factory.DjangoModelFactory):
    class Meta:
        model = Book

    author = factory.SubFactory(AuthorFactory)
    title = factory.Sequence(lambda n: f"Book {n}")


class TestBookSerializer(TestCase):

    def test_deserialize_book(self):
        author = AuthorFactory()
        book_data = BookFactory.build(author=author)

        data = {
            "author": book_data.author_id,
            "title": book_data.title,
        }

        serializer = BookSerializer(data=data)
        self.assertTrue(serializer.is_valid())
        book = serializer.save()

        self.assertEqual(book.author, author)
        self.assertEqual(book.title, book_data.title)

What I’d like is to be able to write the same test without having to create the author before building the book. It would be ideal if there was a strategy I could use that would build the book, but create the dependent author.

In this simple example, it’s easy to say that there’s nothing wrong with just having that additional line where the author is created first, but the problem really comes into play when a model has many related objects, and all of these have to be created for every test.

Proposed solution

In addition to the strategies build, create, and stub, it would be great if there was another strategy that would build the given factory but create the related objects from the corresponding subfactories, maybe something like create_related_and_build. Alternatively if there was a way to plug in custom strategies into factory boy, that could possibly work too.

Extra notes

I apologize if there actually already is a nice way to solve this problem, but I wasn’t able to find anything from some searches on StackOverflow or through the documentation. If that is the case, please consider this issue a request to add an example to the documentation.

Also feel free to tell me that I’m completely thinking about this the wrong way. If that’s the case, I’d be open to suggestions on good patterns for writing tests as I have in my example above.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:1
  • Comments:6

github_iconTop GitHub Comments

2reactions
fedehuguetcommented, May 26, 2020

I am facing the same issue, and I tried solving it by subclassing SubFactory and overriding the generate method, however, I stumbled upon: ValueError: save() prohibited to prevent data loss due to unsaved related object.

I haven’t given it much thought, but basically here’s what I tried:

class ForceCreateSubFactory(SubFactory):
    def generate(self, step, params):
        step.builder.strategy = enums.CREATE_STRATEGY
        subfactory = self.get_factory()
        force_sequence = step.sequence if self.FORCE_SEQUENCE else None
        return step.recurse(subfactory, params, force_sequence=force_sequence)

Has anybody been close to solving it? Or has any ideas on how to approach it?

1reaction
remarkovcommented, Dec 8, 2019

@RevolutionTech Ah! Sorry for misunderstanding from the first place, indeed I missed the point. Now it is clear what you are trying to achieve and this totally makes sense.

For some reason we did not face this problem in our project, probably because of different architecture. I hope one day the proper solution could be implemented in Factory Boy but as a quick and really dirty workaround I would do something like

book_data = BookFactory()
book_data.delete(keep_parents=True)
data = {
    ...
}

This way you will leave all related objects in DB, delete the Book from DB but leave it’s instance in memory for further use in data deserialization. Hope this works!

NB: Not sure you need the keep_parents parameter but I mentioned it just in case you will encounter cascade delete issues. I don’t have Django at hands right now to check this myself but I hope you’ve got the idea and might find it helpful.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Different ways to create objects in Java - GeeksforGeeks
Using the new keyword in java is the most basic way to create an object. This is the most common way to create...
Read more >
Are there any common strategies for choosing an object type ...
I want to create a second object which is related to the first object in the sense that its type is dependant on...
Read more >
Working with objects - JavaScript - MDN Web Docs - Mozilla
Creating new objects ... You can create an object using an object initializer. Alternatively, you can first create a constructor function and then ......
Read more >
Strategy - Refactoring.Guru
Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and...
Read more >
Objects - create instances of types - Microsoft Learn
A program may create many objects of the same class. Objects are also called instances, and they can be stored in either a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found