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 ...
});
Related
I am writing a integration test in my flutter app, it works if I run app. But when I run integration test class, it seems failed to load json file from asset. This is my integration test file
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('login test', () {
testWidgets('full login test', (tester) async {
app.mainCommon("dev");
await tester.pumpAndSettle();
});
});
}
and this is how i load json file
configString = await rootBundle.loadString('config/dev.json');
I'm trying to write an integration test for my Flutter app
I don't want to call the app.main() method over and over for each test
a single app.main() in flutter; How to run all tests using method?
group('Test', () {
testWidgets('Test 1', (tester) async {
app.main();
}
testWidgets('Test 2', (tester) async {
app.main();
}
}
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.
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.
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();
});