factory_boy icon indicating copy to clipboard operation
factory_boy copied to clipboard

Factory and mutable values - changing the object changes values inside Factory

Open wwarne opened this issue 6 years ago • 3 comments

Hello, I couldn't find any topic about behavior like this so I create a new one. I found this because I started to write some tests for a django project which uses postgresql Jsonb fields.

Every time I change the instance of an object created with Traits, the Factory parameters are change too and all newly created instances have this changed values

Here is a simplified example

import factory


class A:
    def __init__(self, json_field):
        self.json_field = json_field


class AFactory(factory.Factory):
    class Meta:
        model = A

    json_field = {'text': 'hello'}

    class Params:
        prefilled = factory.Trait(
            json_field={
                'props': [100, 101, 102, 103, 104],
                'str_property': 'hello',
            }
        )


if __name__ == '__main__':
    item_one = AFactory(prefilled=True)
    print('Item one json_field [created]: {}'.format(item_one.json_field))
    item_one.json_field['props'][0] = 999
    item_one.json_field['str_property'] = 'Goodbye!'
    print('Item one json_field [changed]: {}'.format(item_one.json_field))
    item_two = AFactory(prefilled=True)
    # new instance will have the same json_field as item_one
    print('Item two json_field [created]: {}'.format(item_two.json_field))
    assert item_one.json_field == item_two.json_field

    #without traits
    item_three = AFactory()
    print('Item three json_field [created]: {}'.format(item_three.json_field))
    item_three.json_field['text'] = 'different text'
    print('Item three json_field [changed]: {}'.format(item_three.json_field))
    item_four = AFactory()
    print('Item four json_field [created]: {}'.format(item_four.json_field))
    assert item_three.json_field == item_four.json_field

Item one json_field [created]: {'props': [100, 101, 102, 103, 104], 'str_property': 'hello'}
Item one json_field [changed]: {'props': [999, 101, 102, 103, 104], 'str_property': 'Goodbye!'}
Item two json_field [created]: {'props': [999, 101, 102, 103, 104], 'str_property': 'Goodbye!'}
Item three json_field [created]: {'text': 'hello'}
Item three json_field [changed]: {'text': 'different text'}
Item four json_field [created]: {'text': 'different text'}

P.S. at first I wrote about Traits but then I realised that any mutable values assigned inside Factory class are shared between factory and its instances.

wwarne avatar Sep 26 '19 13:09 wwarne

I'm not sure is it bug or nor, using mutable objects as default values for a class variables is a very bad idea, and I will change my tests to not to do that.

wwarne avatar Sep 26 '19 13:09 wwarne

Hi @wwarne,

Thanks for the report! I don’t think this is a bug, it looks like standard Python behavior. However, it is likely to trip others. Perhaps a note about this gotcha in the Factory class would be useful?

AFAICT, the project does not attempt to copy mutable properties in declarations. Using LazyFunction is encouraged to copy mutable objects.

francoisfreitag avatar Sep 26 '19 13:09 francoisfreitag

Perhaps a note about this gotcha in the Factory class would be useful?

I think it would be great!

Using LazyFunction is encouraged to copy mutable objects.

Thanks again. I have used LazyFunction and everything is perfectly isolated now.

wwarne avatar Sep 26 '19 14:09 wwarne