Adam Griffiths
Adam Griffiths

Reputation: 782

Flutter override environment variables in unit tests

In my production code I set environment variables using the --DART-DEFINE flag and then retrieve them by using String.fromEnvironment.

How can I override the value returned by String.fromEnvironment in my unit tests, to test how my production code responds to different environment variable values?

Upvotes: 8

Views: 3668

Answers (4)

Kira Resari
Kira Resari

Reputation: 2450

I personally would recommend this approach:

class EnvironmentVariables {
  String get flutterAppFlavor =>
      const String.fromEnvironment('FLUTTER_APP_FLAVOR');
}

This builds on what @jamieastley said, but is very lightweight, and in your tests you can easily override it like this:

class EnvironmentVariablesMock implements EnvironmentVariables {
  String _flutterAppFlavor = "";

  @override
  String get flutterAppFlavor => _flutterAppFlavor;

  set flutterAppFlavor(String flutterAppFlavor) =>
      _flutterAppFlavor = flutterAppFlavor;
}

As with the approach of @jamieastley, the idea here would be to implement it into you classes like this:

class MyClass{
  final _environmentVariables = getIt.get<EnvironmentVariables>();

  SomeObject doSomething(){
    var flavor = _environmentVariables.flutterAppFlavor;
    [...]
  }
}

Then you can simply overwrite the environment variables as desired in your tests like this:

void main() {
  late EnvironmentVariablesMock environmentVariablesMock;
  late MyClass myClass;

  setUp(() {
    getIt.reset();
    environmentVariablesMock = EnvironmentVariablesMock();
    getIt.registerSingleton<EnvironmentVariables>(environmentVariablesMock);
    myClass= MyClass ();
  });

  test(
    'doSomething should do something based on flavor',
    () async {
      environmentVariablesMock.flutterAppFlavor = "staging";

      SomeObject response = myClass.doSomething;

      expect(response , equals(SomeObject("The staging value"));
    },
  );
}

Upvotes: 0

jamieastley
jamieastley

Reputation: 610

Wrap your environment variables in a class and use your preferred dependency injection or service locator package (eg. get_it) to access your injected class from within your app.

Your environment_config.dart:

class EnvironmentConfig {

    final String someValue;

    EnvironmentConfig({
      required this.someValue,
    });

    factory EnvironmentConfig.init() {
      const someValue = String.fromEnvironment('someEnvVar');
      return EnvironmentConfig(someValue: someValue);
    }
  
}

main.dart

void main() {
    // instance can now be accessed from any class/widget within your app
    GetIt.I.registerSingleton<EnvironmentConfig>(EnvironmentConfig.init());

    runApp(MyApp());
}

Then within unit tests, you can inject your own instance of EnvironmentConfig with your own values:

void main() {
    setUp(() {
      final config = EnvironmentConfig(someValue: 'myCustomValue');
      GetIt.I.registerSingleton<EnvironmentConfig>(config);
    });

    tearDown(() {
      GetIt.I.reset();
    });

    test('Verify env vars', () {
      expect(GetIt.I<EnvironmentConfig>().someValue, equals('myCustomValue');
    });
}

EDIT: Given this is now the top result when searching for an answer to this, it's worth noting that the trade-off to the above approach is that it doesn't cater for tree-shaking, as the EnvironmentConfig class is not a compile-time constant when using the factory constructor.

Upvotes: 5

Worik
Worik

Reputation: 152

There are two good answers here, but they do miss the point.

The point is:

The Question:

How can I override the value returned by String.fromEnvironment in my unit tests

The onpoint, correct answer is:

You cannot do that.

You have to modify and complicate your code to make it testable. The data structure that holds environmental data in Dart is read only, so it is impossible to do what you require in this language.

Upvotes: 0

PixelToast
PixelToast

Reputation: 965

The flutter test command supports --dart-define=NAME=VALUE, but this is annoying to automate. It's also not possible to change dart defines at runtime.

What my team does is declare all of the dart defines in a single file, and use separate global variables to override them during tests:

String? fooDefineOverride;
String get fooDefine => fooDefineOverride ?? const String.fromEnvironment('foo');

void resetOverrides() {
  fooDefineOverride = null;
}
void main() {
  setUpAll(() {
    resetOverrides();
  });

  test('Foo is foobar', () {
    fooDefineOverride = 'foobar';
    // ... do tests
  });
}

Upvotes: 3

Related Questions