Flutter integration_test - initialising and disposing dependencies - flutter

I’m initializing the app like
void main() async {
await Initializer.init();
runApp(MyApp());
}
Initializer.init() initializes all dependencies like Firebase Analytics and register things like Hive TypeAdapters.
My sample tests look like:
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets(
'Sample',
(WidgetTester tester) async {
await Initializer.init();
await tester.pumpWidget(MyApp());
await tester.tap(find.text('Something'));
expect(find.text('180 Results'), findsOneWidget);
},
);
testWidgets(
'Sample 2',
(WidgetTester tester) async {
await Initializer.init();
await tester.pumpWidget(MyApp());
await tester.tap(find.text('Something 2'));
expect(find.text('180 Results'), findsOneWidget);
},
);
}
My problem is that after executing the first test, things that were initialized by Initializer.init() aren’t disposed correctly.
Is there a way to tell integration_test to reset the whole environment every time test is reloaded?
Shall I initialize things differently than static Initializer.init();
Shall I explicitly dispose/unregister all dependencies (sth like Initializer.dispose() at the end of every test)? That solution seems to be a nightmare since I'm using many 3rd parties that are not allowing explicit disposing.

Related

Stubbing TargetPlatform not working in flutter test

I have these 2 tests:
void main() {
group('...', () {
testWidgets('Should ... when the OS is macOS', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
theme: ThemeData(
platform: TargetPlatform.macOS,
),
));
...
assert(Theme.of(context).platform...); // platform is android!!
});
testWidgets('Should ... when the OS is windows', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
theme: ThemeData(
platform: TargetPlatform.windows,
),
));
...
assert(Theme.of(context).platform...); // platform is android!!
});
});
}
I overrided the platform in the theme of MaterialApp explicitly as per the docs:
In a test environment, the platform returned is TargetPlatform.android
regardless of the host platform. (Android was chosen because the tests
were originally written assuming Android-like behavior, and we added
platform adaptations for other platforms later). Tests can check
behavior for other platforms by setting the platform of the Theme
explicitly to another TargetPlatform value, or by setting
debugDefaultTargetPlatformOverride.
I also tried setting it via debugDefaultTargetPlatformOverride, like this:
void main() {
group('...', () {
testWidgets('Should ... when the OS is macOS', (WidgetTester tester) async {
// set the OS to macOS
debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
await tester.pumpWidget(const MaterialApp());
await tester.pumpAndSettle();
BuildContext context = tester.element(find.byType(MaterialApp));
assert(Theme.of(context).platform...); // Works, platform is macOS
// reset the default target platform
debugDefaultTargetPlatformOverride = null;
});
testWidgets('Should ... when the OS is windows', (WidgetTester tester) async {
// set the OS to windows
debugDefaultTargetPlatformOverride = TargetPlatform.windows;
await tester.pumpWidget(const MaterialApp());
await tester.pumpAndSettle();
BuildContext context = tester.element(find.byType(MaterialApp));
assert(Theme.of(context).platform...); //Platform is still macOS!!
// reset the default target platform
debugDefaultTargetPlatformOverride = null;
});
});
}
But in the code above the first test only passes but the second fails bcz the platform resetting is not working and the platform is still macOS (due to the first test).
What am I doing wrong?

Why is Future in this test never completing?

I was testing a service I wrote which returns a Future, but this weird behavior happened when I awaited the result of the future and the test never completed. Trying to reproduce the error in a minimal way, I got this:
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('...',
(WidgetTester tester) async {
print('awaiting');
await Future.delayed(Duration(seconds: 2)); // never completes
print('done'); // not reached
});
}
but this completes normally:
import 'package:flutter_test/flutter_test.dart';
void main() {
test('...',
() async {
print('awaiting');
await Future.delayed(Duration(seconds: 2)); // completes normally
print('done'); // prints "done"
});
}
why is the first one not completing, what am I missing?
using tester.runAsync solves the problem (I still don't know why I need to run my function within this function). It is used like this as per this github issue:
await tester.runAsync(() async {
// run my function here
});

Run mutliple integration tests in Flutter

I have two integrations test files: login_test.dart and homepage_test.dart (each including single test case with 1 testWidgets method)
I'm using command line to run both tests:
flutter test integration_test/
But the problem is when the test has run and passed login_test . it completely reinstalled my app so second test will fail because have no login credentials (stuck at login screen -> failed)
Any idea how I can solve this ?
Appreciate your helps.
login_test.dart
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end to end tests', () {
testWidgets('test login', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
final Finder textFieldFinder = find.byType(TextFormField);
expect(textFieldFinder, findsNWidgets(2));
final userName = textFieldFinder.first;
final password = textFieldFinder.last;
await tester.enterText(userName, 'myusername');
await tester.enterText(password, 'mypassword');
expect(find.text('Login'), findsOneWidget);
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();
expect(find.byKey(Key('createTicketBtn')), findsOneWidget);
});
});
}
homepage_test.dart
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end to end tests', () {
testWidgets('test homepage', (tester) async {
app.main();
await tester.pumpAndSettle();
expect(find.byKey(Key('createTicketBtn')), findsOneWidget);
});
});
}
You can use this package to scan a folder inside the integration_test directory for files named like *_test.dart. This will produce a file with a new main importing all test files, so you can just run this new generated file.
You can also create manually a file like this and launch it:
import homepage_test.dart as homepage;
import login_test.dart as login;
void main() {
homepage.main();
login.main();
}
In any case, each test will start from the beginning so you need to login each time. The app will not lose its data though, unless you reset its state in the tearDown.

Flutter integration tests - multiple tests

So I recently wanted to add some integration testing to the my app, and followed the official guidelines from the flutter.dev on the integration testing. I managed to run a single test, so I wanted to add another one, and this is where the problems started.
I don't know how to add another test suite in the same run, so something like
TEST 1:
click button
check if counter incremented
TEST2:
click the button again
check if counter incremented again
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Integration test', () {
testWidgets('test 1', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
// doing stuff here works
});
});
}
This is what I have working right now. I expected that moving the app.main() call before the group and simply adding another testWidgets call will work (similar to unit testing) but it doesn't:
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// app.main(); <<--- moving here...
group('Integration test', () {
// app.main(); <<--- ... or here didn't help
testWidgets('test 1', (WidgetTester tester) async {
// do stuff
});
testWidgets('test 2', (WidgetTester tester) async {
// how to add this? And do stuff with the same app session
});
});
}
EDIT:
For better clarity of what I'm trying to achieve. The following are some current flutter driver tests that I have. I just want to migrate to the new api of integration_test package, without losing the names "test 1", "test 2" etc
void main() {
group("group name", () {
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
await driver.close();
}
});
test('test 1', () async {
// do stuff with the driver
});
test('test 2', () async {
// do other stuff with the driver with the same seesion
});
test('test 3', () async {
// etc
});
});
}
I assume your app.main() is booting the actual app. It is not possible to boot the app just once at the start of a group. You have to boot the app within the testWidgets().
It might be useful to create a restartApp() which can be triggered at the start of every testWidgets(). This way you'll be able to start your new test fresh from the start.
You should add an UniqueKey() to the app root widget. This UniqueKey() needs to be re-generated after the restartApp(). Your app will recognise the key being changed so it will perform a reload.
testWidgets('test 1', (WidgetTester tester) async {
restartApp();
clickButton();
// expect count incremented
});
testWidgets('test 2', (WidgetTester tester) async {
restartApp();
clickButton();
// expect count incremented
clickButton();
// expect count incremented again
});
Here is how I run multiple test using integration_test, the SharedPreferences data seems to be preserved between test. (doesn't work with multiple group). I just recently migrated from using raw test driver to integration_test.
You have to start the app in setUp block, and simply write the test in different testWidgets.
Note: This will throw sqlite error since there is no way to make sure the app is close and reopen and this will open a new instance for each testWidget. Let me know if anyone know how to work around this issue.
import 'package:my_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
// this will start the app for each subsequent test
app.main();
});
group('Smoke Test', () {
testWidgets('Integration 1', (WidgetTester tester) async {
...
});
testWidgets('Integration 2', (WidgetTester tester) async {
...
});
);
}
After the first test, I've got subsequent test cases to run successfully by pumping the main app into the test, e.g.
import 'package:my_app/main.dart' as app;
testWidgets('Test 1', (WidgetTester tester) {
app.main();
await tester.pumpAndSettle();
// Tests ...
});
testWidgets('Test 2', (WidgetTester tester) {
await tester.pumpWidget(app.MyApp());
await tester.pumpAndSettle();
// Tests ...
});

Flutter Integration testing failed for multiple test cases in a single file

I have a simple login page with email and password text field. A button to login and another to sign up. I tried to write integration testing for the Sign in page.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('''could type email and password in text filed''',
(WidgetTester tester) async {
await app.main();
await tester.pumpAndSettle();
final textFieldEmail = find.byType(InputTextWidget).first;
final textFieldPassword = find.byType(InputTextWidget).last;
await tester.enterText(textFieldEmail, "asis.adh#gmail.com");
await tester.pumpAndSettle();
expect(find.text("asis.adh#gmail.com"), findsOneWidget);
});
testWidgets(
'should redirect to Sign Up Page when create an account is tapped',
(WidgetTester tester) async {
await app.main();
await tester.pumpAndSettle();
final createAnAccount = find.text("Create an account");
await tester.tap(createAnAccount);
await tester.pumpAndSettle();
expect(find.byType(SignupPage), findsOneWidget);
expect(find.byType(LoginPage), findsNothing);
});
}
When I execute the test case, it fails with the following error:
The following ArgumentError was thrown running a test: Invalid
argument(s): Object/factory with type AmenitiesProvider is already
registered inside GetIt.
Here is my main.dart file
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
configureInjection(Environment.prod);
/// for registering the factory.
await Future.delayed(const Duration(seconds: 2));
runApp(RoopaApp());
}
I tried with a main_test.dart and adding configureInjection(Environment.test) but nothing changes. I am not sure how to fix the error. Is there a way to clean the app or destroy it before going to new test case. If I combine the both testcase into one then it works without any problem.
Here is configureInjection
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
}
I am using get_it and injection package for dependency injection.
Here is the auto generated initGetIt
GetIt $initGetIt(
GetIt get, {
String environment,
EnvironmentFilter environmentFilter,
}) {
final gh = GetItHelper(get, environment, environmentFilter);
final httpClientInjectableModule = _$HttpClientInjectableModule();
final flutterStorageModule = _$FlutterStorageModule();
gh.lazySingleton<AmenitiesProvider>(() => AmenitiesProvider());
gh.factory<Client>(() => httpClientInjectableModule.client);
gh.lazySingleton<FileProvider>(() => FileProvider());
gh.lazySingleton<FlutterSecureStorage>(
() => flutterStorageModule.secureStorate);
gh.factory<ProfilePageBloc>(() => ProfilePageBloc());
gh.factory<SplashScreenBloc>(() => SplashScreenBloc());
gh.lazySingleton<AuthLocalDataSourceProtocol>(
() => AuthLocalDataSource(secureStorage: get<FlutterSecureStorage>()));
gh.lazySingleton<AuthRemoteDataSourceProtocol>(
() => AuthRemoteDataSource(client: get<Client>()));
return get;
}
In my config page.
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
}
I just created a test environment and added following like, now its working as expected.
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
if (environment == Environment.test) {
getIt.allowReassignment = true;
}
}
tearDown(() async {
final getIt = GetIt.instance;
await getIt.reset();
});