Reusing i18n strings for IT testing in Flutter

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.

Please follow and like us:
error