7
votes

I'm internationalizing/i18n-ing a django project. We have one part that runs independently and performs background tasks. It's called by rabbitmq. I want to test that the i18n/l10n works for that part. However our app isn't translated yet, and won't be for a while. I want to write the unittests before translation begins.

I'd like to mock some translations, so that _("anything") is returned as a constant string, so that I can test that it's trying to translate things, without me needing to set up all the translations.

I tried using mock, but with mock.patch('django.utils.translations.ugettext_lazy'), my_function_that_just_returns_one_string): didn't work. The _ is imported as from django.utils.translations import ugettext_lazy as _.

4
Sorry for weird question, but why do you want to test something which is already tested - github.com/bx2/django/blob/master/tests/regressiontests/i18n/…? Basic rules of TDD teach us that we have to trust other developers. I think that what you are trying to od is an overkill. Django's i18n framework has been proven working and it is tested for that - why complicate things more? ;)bx2
My code uses the django translations, but it runs in a separate process, not serving web requests, I'd like to test that it is translating things in different requests.Amandasaurus

4 Answers

2
votes

I couldn't find an existing way to do this. However from reading the Django source code I came up with a hacky, brittle way to do this by looking at the _active DjangoTranslation objects, then wrapping their ugettext methods. I've described it here: http://www.technomancy.org/python/django-i18n-test-translation-by-manually-setting-translations/

2
votes

I've looked at your solution and I think it's both ingenious and simple for testing i18n support when you have no translation strings provided. But I'm afraid the translation package is just something that always works and which we take for granted, so seeing it's internals in heavily commented test code would, at least, make me run off in fear (chuckle).

I think creating a test application, added to INSTALLED_APPS in test settings, which provides it's own translations is a much cleaner approach. Your tests would be simplified to translation.activate('fr'); self.assertEqual('xxx_anything', gettext('anything'), 'i18n support should be activated.').

With simple tests, other developers could quickly follow-up and see that the test app's package contains a /locale directory, which should immediately document your approach.

2
votes

You can do the following to replace the ugettext method on the default translation object:

from django.utils.translation.trans_real import get_language, translation

translation(get_language()).ugettext = mock_ugettext
0
votes

It seems you're not patching the correct module. If it's your foo/models.py which has the from django.utils.translations import ugettext_lazy as _ statement, then _ is in the namespace of the foo.models module, and this is where you have to patch.

with mock.patch('foo.models._', return_value='MOCKED_TRANSLATION'):
    ...

or

with mock.patch('foo.models._') as mock_ugettext_lazy:
    mock_ugettext_lazy.side_effect = lambda x: x + ' translated'
    ...
    assert translated_text = 'example_text translated'

If you have multiple modules using ugettext_lazy, then you can do it like so:

with mock.patch('foo.models._', side_effect=mock_translator), \
     mock.patch('bar._', side_effect=mock_translator):
    ...

Unfortunately, there is no one-liner to mock it for all modules that use ugettext_lazy, because once the function is imported in your modules, it's pointless to change django.utils.translations.ugettext_lazy -- the original references will keep pointing to the original function.

See https://docs.python.org/3/library/unittest.mock.html#where-to-patch for more.