Reusing i18n strings for IT testing in
It is not uncommon to check the text of some widgets when doing integration testing. It is usually done so, in order to be assured that the user will receive the appropriate feedback before and/or after certain actions.
TL;DR
Check out the full example here, but I would suggest you keep reading, it will not take that long 🙂
The use case
Let’s take the default counter application you get whenever you generate a new flutter application.
There is a text (‘You have pushed the button this many times:’) that we really want to make sure it stays there cause it gives valuable feedback to the user. We want to make sure that this text is always visible and we could achieve that with the following integration test.
Provisioning for i18n
That would be sufficient for a small app such as this, but what in reality apps are way bigger and more complex than this. For those apps, it is common to provision for i18n, because it will widen the app’s audience.
The most common implementation you will find in an i18n flutter app is the one the documentation is actually suggesting. In our case for the counter app, it would look like the following listing.
Try to use AppLocalization in Integration Test
Now that we have created our AppLocalization
we could, in theory, use this component in our integration tests in order to have a more static reference to a text, through the counterText
getter.
So, we look at our integration test and we think that this is it! We feel confident and proud that we are going to test the text in using a static reference and not the literal string.
Well … think again! Lo and behold we have the following error!
test_driver/counter_it_test.dart:1:8: Error: Not found: 'dart:ui'
What is this error all about?
So apparently we should not import dart:ui
directly or indirectly to the test_driver
integration test. For more information on this issue check out this.
We can get away with the direct importing, but it is not that simple for the indirect one, as the import could lie several layers under our direct import.
How to fix it
Alright, so in theory, we could get away with using AppLocalization
in integration tests, if we could abstract all the static references and translations into a module that has no dependencies.
One solution I managed to come up with is to use a mixin
to keep the _localizedValues
and getter methods. But this would not work by itself as we need a locale in order to resolve the correct i18n value for the getter.
String get counterText => _localizedValues[locale.toString()]['counter_text'];
This locale
variable should be final
and accessed by the mixi
. So, we could introduce an abstract class
to hold the final locale
and bind it to the mixin
by using the on
notation in the mixin
declaration. Confused? Let's look at some code to make it clearer.
Using the LocalizationsProvider
So, now all we have to do is to make AppLocalization
inherit this functionality we have abstracted to LocalizationsProvider
. It is as simple as this:
As for the integration tests, we can just have a test concrete class extending LocalizationsProvider
...
class TestLocalization extends LocaleCodeAware with LocalizationsProvider { TestLocalization(String localeCode) : super(localeCode); }
… and use that one in our tests
What we have achieved is that we are now able to use our i18n texts in a statically referenced way, in our integration tests. Furthermore, if a text change is needed it will only happen in LocalizationsProvider
and it will be depicted both in the application and in tests without any other work needed.
You can find the complete example in this repo.