FactoryBoy / factory_boy

A test fixtures replacement for Python
https://factoryboy.readthedocs.io/
MIT License
3.48k stars 392 forks source link

Change default faker locale in factory_boy #407

Open 3ynm opened 7 years ago

3ynm commented 7 years ago

How can I set the default locale in Python's factory_boy for all of my Factories?

In docs says that one should set it with factory.Faker.override_default_locale but that does nothing to my fakers...

import factory
from app.models import Example
from custom_fakers import CustomFakers

# I use custom fakers, this indeed are added
factory.Faker.add_provider(CustomFakers)
# But not default locales
factory.Faker.override_default_locale('es_ES')

class ExampleFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Example

    name = factory.Faker('first_name')

>>> from example import ExampleFactory
>>> e1 = ExampleFactory()
>>> e1.name
>>> u'Chad'
rbarrois commented 7 years ago

Indeed: factory.Faker.override_default_locale() is a decorator and context manager: you can only use it as:

with factory.Faker.override_default_locale('es_ES'):
    e1 = ExampleFactory()

Or:

@factory.Faker.override_default_locale('es_ES')
def test_foo(self):
    e1 = ExampleFactory()

The idea is that changing the default locale is mostly used for a few specific tests while the whole suite doesn't care; hence, having a decorator/context manager ensures that the default locale is restored once those tests have run.

However, this is not very clear in the docs; I think we should improve them. Alternatively, we could add an extra entrypoint to set the global, initial configuration for Faker (including adding extra providers).

3ynm commented 6 years ago

Indeed I think allowing to set a global language is a must, thanks :)

By the way, global configuration for additional provider does work.

elbenfreund commented 6 years ago

I agree with @Hacktivista that an easy (and preferably randomizable) way to change the locale would be a major advantage. Right now I am considering using vanilla faker via a custom fake fixture that does just that. It would be nice to have something similar with factory_boy's otherwise lovely faker integration.

If thats something you would be interested in getting a PR please let me know.

chriswyatt commented 5 years ago

This is something I'd also find useful. I also think it might be useful to be able to attach an existing faker generator, so I filed a ticket: https://github.com/FactoryBoy/factory_boy/issues/554

x-yuri commented 5 years ago

See workaround here.

x-yuri commented 3 years ago

I was about to create a new issue. But found this one. Even with some feedback from me :)

I looked into it once again. There are basically 2 ways.

As one might find out, the default locale is taken from faker. But setting the default locale is not covered by the faker's documentation. The good news is that with faker it's probably not that important, since usually you specify the locale once, when you instantiate the generator (e.g. Faker(locale='...')). I tried looking into changing faker's default, but first, faker's and factory-boy's default locales are default in different ways. Second, I couldn't easily find a way such that I'd be sure I don't introduce issues.

Another option is to do:

factory.Faker._DEFAULT_LOCALE = '...'

after importing factory. And apparently before declaring factories. This one will probably work out. But I don't like this underscore in front of the name. And the fact that it's not documented.

@rbarrois Do you think the second way can be considered official (except for underscore)? Or do you have any better suggestions?

UPD But considering that Django project may contain several applications. It doesn't seem right to change the default locale in just one of the them. Some centralized way would probably be preferable.

rbarrois commented 3 years ago

So, let's look at the different situations where one might want to change the default locale:

For the last two cases, we'll have to look into the program setup:

For Django, I believe that the recommended mode is to:

  1. Subclass Django's DiscoverRunner
  2. Overload its run_tests function, to wrap the initial method inside a with override_default_locale()
  3. Point your settings' TEST_RUNNER to your class:
# settings.py
TEST_RUNNER = 'myproject.testing.MyTestRunner'

# myproject/testing.py
import factory
from django.conf import settings
from django.util import translation
import django.test.runner

class MyTestRunner(django.test.runner.DiscoverRunner):
    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        with factory.Faker.override_default_locale(translation.to_locale(settings.LANGUAGE_CODE)):
            return super().run_tests(test_labels, extra_tests=extra_tests, **kwargs)
x-yuri commented 3 years ago

@rbarrois Oh, it appears I've got my case covered. Do you think we can document it? Supposedly by adding a common recipe?

robvdl commented 3 years ago

For pytest django I just put it in conftest.py since that loads before any tests start:

import faker.config

faker.config.DEFAULT_LOCALE = "en_NZ"

Works a charm, if you check the Faker class, it populates _DEFAULT_LOCALE from there.

x-yuri commented 3 years ago

Indeed, just importing factory or faker doesn't make use of the faker.config.DEFAULT_LOCALE value. So sounds safe.

Although I'd like to point out at a slight distinction between factory-boy's and faker's default locales. For factory-boy the default locale is a locale used when no locale is specified. That is true for faker as well. But in addition faker falls back to the default locale if a provider doesn't support the locale you demanded and doesn't provide its own fallback. For now the only unlocalized provider that doesn't support en_US is the bank provider, but it provides a fallback.

In other words, changing factory-boy's and faker's default locales might have different effects.