summaryrefslogtreecommitdiff
path: root/docs/recipes.rst
blob: 70eca46be66b8e34ac20a4f0a60f646ade62c836 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
Common recipes
==============


.. note:: Most recipes below take on Django model examples, but can also be used on their own.


Dependent objects (ForeignKey)
------------------------------

When one attribute is actually a complex field
(e.g a :class:`~django.db.models.ForeignKey` to another :class:`~django.db.models.Model`),
use the :class:`~factory.SubFactory` declaration:


.. code-block:: python

    # models.py
    class User(models.Model):
        first_name = models.CharField()
        group = models.ForeignKey(Group)


    # factories.py
    import factory
    from . import models

    class UserFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.User

        first_name = factory.Sequence(lambda n: "Agent %03d" % n)
        group = factory.SubFactory(GroupFactory)


Reverse dependencies (reverse ForeignKey)
-----------------------------------------

When a related object should be created upon object creation
(e.g a reverse :class:`~django.db.models.ForeignKey` from another :class:`~django.db.models.Model`),
use a :class:`~factory.RelatedFactory` declaration:


.. code-block:: python

    # models.py
    class User(models.Model):
        pass

    class UserLog(models.Model):
        user = models.ForeignKey(User)
        action = models.CharField()


    # factories.py
    class UserFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.User

        log = factory.RelatedFactory(UserLogFactory, 'user', action=models.UserLog.ACTION_CREATE)


When a :class:`UserFactory` is instantiated, factory_boy will call
``UserLogFactory(user=that_user, action=...)`` just before returning the created ``User``.


Example: Django's Profile
"""""""""""""""""""""""""

Django (<1.5) provided a mechanism to attach a ``Profile`` to a ``User`` instance,
using a :class:`~django.db.models.OneToOneField` from the ``Profile`` to the ``User``.

A typical way to create those profiles was to hook a post-save signal to the ``User`` model.

factory_boy allows to define attributes of such profiles dynamically when creating a ``User``:

.. code-block:: python

    class ProfileFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = my_models.Profile

        title = 'Dr'
        # We pass in profile=None to prevent UserFactory from creating another profile
        # (this disables the RelatedFactory)
        user = factory.SubFactory('app.factories.UserFactory', profile=None)

    class UserFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = auth_models.User

        username = factory.Sequence(lambda n: "user_%d" % n)

        # We pass in 'user' to link the generated Profile to our just-generated User
        # This will call ProfileFactory(user=our_new_user), thus skipping the SubFactory.
        profile = factory.RelatedFactory(ProfileFactory, 'user')

        @classmethod
        def _generate(cls, create, attrs):
            """Override the default _generate() to disable the post-save signal."""

            # Note: If the signal was defined with a dispatch_uid, include that in both calls.
            post_save.disconnect(handler_create_user_profile, auth_models.User)
            user = super(UserFactory, cls)._generate(create, attrs)
            post_save.connect(handler_create_user_profile, auth_models.User)
            return user

.. OHAI_VIM:*


.. code-block:: pycon

    >>> u = UserFactory(profile__title=u"Lord")
    >>> u.get_profile().title
    u"Lord"

Such behaviour can be extended to other situations where a signal interferes with
factory_boy related factories.

.. note:: When any :class:`~factory.RelatedFactory` or :class:`~factory.post_generation`
          attribute is defined on the :class:`~factory.django.DjangoModelFactory` subclass,
          a second ``save()`` is performed *after* the call to ``_create()``.

          Code working with signals should thus override the :meth:`~factory.Factory._generate`
          method.


Simple ManyToMany
-----------------

Building the adequate link between two models depends heavily on the use case;
factory_boy doesn't provide a "all in one tools" as for :class:`~factory.SubFactory`
or :class:`~factory.RelatedFactory`, users will have to craft their own depending
on the model.

The base building block for this feature is the :class:`~factory.post_generation`
hook:

.. code-block:: python

    # models.py
    class Group(models.Model):
        name = models.CharField()

    class User(models.Model):
        name = models.CharField()
        groups = models.ManyToMany(Group)


    # factories.py
    class GroupFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.Group

        name = factory.Sequence(lambda n: "Group #%s" % n)

    class UserFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.User

        name = "John Doe"

        @factory.post_generation
        def groups(self, create, extracted, **kwargs):
            if not create:
                # Simple build, do nothing.
                return

            if extracted:
                # A list of groups were passed in, use them
                for group in extracted:
                    self.groups.add(group)

.. OHAI_VIM**

When calling ``UserFactory()`` or ``UserFactory.build()``, no group binding
will be created.

But when ``UserFactory.create(groups=(group1, group2, group3))`` is called,
the ``groups`` declaration will add passed in groups to the set of groups for the
user.


ManyToMany with a 'through'
---------------------------


If only one link is required, this can be simply performed with a :class:`RelatedFactory`.
If more links are needed, simply add more :class:`RelatedFactory` declarations:

.. code-block:: python

    # models.py
    class User(models.Model):
        name = models.CharField()

    class Group(models.Model):
        name = models.CharField()
        members = models.ManyToMany(User, through='GroupLevel')

    class GroupLevel(models.Model):
        user = models.ForeignKey(User)
        group = models.ForeignKey(Group)
        rank = models.IntegerField()


    # factories.py
    class UserFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.User

        name = "John Doe"

    class GroupFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.Group

        name = "Admins"

    class GroupLevelFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.GroupLevel

        user = factory.SubFactory(UserFactory)
        group = factory.SubFactory(GroupFactory)
        rank = 1

    class UserWithGroupFactory(UserFactory):
        membership = factory.RelatedFactory(GroupLevelFactory, 'user')

    class UserWith2GroupsFactory(UserFactory):
        membership1 = factory.RelatedFactory(GroupLevelFactory, 'user', group__name='Group1')
        membership2 = factory.RelatedFactory(GroupLevelFactory, 'user', group__name='Group2')


Whenever the ``UserWithGroupFactory`` is called, it will, as a post-generation hook,
call the ``GroupLevelFactory``, passing the generated user as a ``user`` field:

1. ``UserWithGroupFactory()`` generates a ``User`` instance, ``obj``
2. It calls ``GroupLevelFactory(user=obj)``
3. It returns ``obj``


When using the ``UserWith2GroupsFactory``, that behavior becomes:

1. ``UserWith2GroupsFactory()`` generates a ``User`` instance, ``obj``
2. It calls ``GroupLevelFactory(user=obj, group__name='Group1')``
3. It calls ``GroupLevelFactory(user=obj, group__name='Group2')``
4. It returns ``obj``


Copying fields to a SubFactory
------------------------------

When a field of a related class should match one of the container:


.. code-block:: python

    # models.py
    class Country(models.Model):
        name = models.CharField()
        lang = models.CharField()

    class User(models.Model):
        name = models.CharField()
        lang = models.CharField()
        country = models.ForeignKey(Country)

    class Company(models.Model):
        name = models.CharField()
        owner = models.ForeignKey(User)
        country = models.ForeignKey(Country)


Here, we want:

- The User to have the lang of its country (``factory.SelfAttribute('country.lang')``)
- The Company owner to live in the country of the company (``factory.SelfAttribute('..country')``)

.. code-block:: python

    # factories.py
    class CountryFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.Country

        name = factory.Iterator(["France", "Italy", "Spain"])
        lang = factory.Iterator(['fr', 'it', 'es'])

    class UserFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.User

        name = "John"
        lang = factory.SelfAttribute('country.lang')
        country = factory.SubFactory(CountryFactory)

    class CompanyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.Company

        name = "ACME, Inc."
        country = factory.SubFactory(CountryFactory)
        owner = factory.SubFactory(UserFactory, country=factory.SelfAttribute('..country'))


Custom manager methods
----------------------

Sometimes you need a factory to call a specific manager method other then the
default :meth:`Model.objects.create() <django.db.models.query.QuerySet.create>` method:

.. code-block:: python

   class UserFactory(factory.DjangoModelFactory):
       class Meta:
           model = UserenaSignup

       username = "l7d8s"
       email = "my_name@example.com"
       password = "my_password"

       @classmethod
       def _create(cls, model_class, *args, **kwargs):
           """Override the default ``_create`` with our custom call."""
           manager = cls._get_manager(model_class)
           # The default would use ``manager.create(*args, **kwargs)``
           return manager.create_user(*args, **kwargs)


Forcing the sequence counter
----------------------------

A common pattern with factory_boy is to use a :class:`factory.Sequence` declaration
to provide varying values to attributes declared as unique.

However, it is sometimes useful to force a given value to the counter, for instance
to ensure that tests are properly reproductible.

factory_boy provides a few hooks for this:


Forcing the value on a per-call basis
    In order to force the counter for a specific :class:`~factory.Factory` instantiation,
    just pass the value in the ``__sequence=42`` parameter:

    .. code-block:: python

        class AccountFactory(factory.Factory):
            class Meta:
                model = Account
            uid = factory.Sequence(lambda n: n)
            name = "Test"

    .. code-block:: pycon

        >>> obj1 = AccountFactory(name="John Doe", __sequence=10)
        >>> obj1.uid  # Taken from the __sequence counter
        10
        >>> obj2 = AccountFactory(name="Jane Doe")
>>> obj2.uid  # The base sequence counter hasn't changed
                1


Resetting the counter globally
    If all calls for a factory must start from a deterministic number,
    use :meth:`factory.Factory.reset_sequence`; this will reset the counter
    to its initial value (as defined by :meth:`factory.Factory._setup_next_sequence`).

    .. code-block:: pycon

        >>> AccountFactory().uid
        1
        >>> AccountFactory().uid
        2
        >>> AccountFactory.reset_sequence()
>>> AccountFactory().uid  # Reset to the initial value        
        1
        >>> AccountFactory().uid
        2

    It is also possible to reset the counter to a specific value:

    .. code-block:: pycon

        >>> AccountFactory.reset_sequence(10)
        >>> AccountFactory().uid
        10
        >>> AccountFactory().uid
        11

    This recipe is most useful in a :class:`~unittest.TestCase`'s
    :meth:`~unittest.TestCase.setUp` method.


Forcing the initial value for all projects
    The sequence counter of a :class:`~factory.Factory` can also be set
    automatically upon the first call through the
    :meth:`~factory.Factory._setup_next_sequence` method; this helps when the
    objects's attributes mustn't conflict with pre-existing data.

    A typical example is to ensure that running a Python script twice will create
    non-conflicting objects, by setting up the counter to "max used value plus one":

    .. code-block:: python

        class AccountFactory(factory.django.DjangoModelFactory):
            class Meta:
                model = models.Account

            @classmethod
            def _setup_next_sequence(cls):
                try:
                    return models.Accounts.objects.latest('uid').uid + 1
                except models.Account.DoesNotExist:
                    return 1

    .. code-block:: pycon

        >>> Account.objects.create(uid=42, name="Blah")
        >>> AccountFactory.create()  # Sets up the account number based on the latest uid
        <Account uid=43, name=Test>