dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
Tạo class SessionHelper xử lý lưu trữ thông tin đăng nhập
class SessionHelper {
static Member? login(String email, String password) {
if (email == "thanh@ceb.net.vn" && password == "123") {
Random rand = Random();
return Member(
id: rand.nextInt(99999) + 9999,
name: "thanh",
email: email,
role: "member",
);
}
return null;
}
static Future<void> save(Member obj) async {
SharedPreferences shared = await SharedPreferences.getInstance();
shared.setString('member', jsonEncode(obj.toMap()));
}
static Future<Member?> get member async {
SharedPreferences shared = await SharedPreferences.getInstance();
String? result = shared.getString('member');
return result != null ? Member.fromMap(jsonDecode(result)) : null;
}
static Future<void> clear() async {
SharedPreferences shared = await SharedPreferences.getInstance();
await shared.remove('member');
}
}
Tạo class HomePage là trang chủ hiện thị thông tin trước và sau khi đăng nhập
class HomePage extends StatefulWidget {
final String title;
const HomePage({super.key, required this.title});
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Member? _member;
@override
void initState() {
super.initState();
_initData();
}
Future<void> _initData() async {
Member? obj = await SessionHelper.member;
setState(() {
_member = obj;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Column(
children: [
if (_member != null) ...[
ElevatedButton.icon(onPressed: _logout, label: Text('Logout')),
ElevatedButton.icon( onPressed: _profile, label: Text(_member!.name)),
] else ...[
ElevatedButton.icon(onPressed: _login, label: Text('Login')),
],
],
),
);
}
void _profile() {}
void _logout() async {
await SessionHelper.clear();
if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar();
setState(() {
_member = null;
});
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Logout Success')));
}
void _login() async {
var result = await Navigator.pushNamed(context, '/login');
if (result == true) {
await _initData();
}
}
}
Tạo class Login Page xử lý đăng nhập thông tin người dùng.
class LoginPage extends StatefulWidget {
final String _title;
const LoginPage({super.key, required this._title});
@override
State<StatefulWidget> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
String? _msg;
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget._title)),
body: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
validator: _validateEmail,
key: Key("email"),
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
),
TextFormField(
validator: _validatePassword,
key: Key("password"),
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
),
ElevatedButton(
key: Key("login"),
onPressed: _login,
child: Text('Login'),
),
if (_msg != null) Text(_msg!),
],
),
),
);
}
String? _validatePassword(String? password) {
if (password == null || password.isEmpty) return "Please enter password";
if (password.length < 3) return "Please enter password min 3 character";
return null;
}
String? _validateEmail(String? email) {
if (email == null || email.isEmpty) return "Please enter email";
RegExp regExp = RegExp(r'^[a-z][a-z0-9]+@[a-z0-9]{2,}(\.[a-z]{2,})+$');
return regExp.hasMatch(email) ? null : "Email invalid";
}
void _login() async {
if (_formKey.currentState!.validate()) {
String email = _emailController.text.trim();
String password = _passwordController.text.trim();
Member? member = SessionHelper.login(email, password);
if (member != null) {
await SessionHelper.save(member);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: const Text('Login Success')));
Navigator.pop(context, true);
} else {
_msg = "Login Failed";
}
}
}
}
Trong hàm main xử lý quản lý định tuyến giữa các trang HomePage và LoginPage
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
initialRoute: '/',
routes: {
'/': (context) => HomePage(title: 'Home Page'),
'/login': (context) => LoginPage(title: 'Login Page'),
},
);
}
}
Tạo thư mục integration_test và tạo file app_test.dart với nội dung như sau:
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets('Login Test', (tester) async {
app.main();
await tester.pumpAndSettle();
expect(find.text('Login'), findsOneWidget);
expect(find.text('Logout'), findsNothing);
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();
expect(find.text('Login Page'), findsOne);
await tester.enterText(find.byKey(const Key('email')), 'thanh@ceb.net.vn');
await tester.enterText(find.byKey(const Key('password')), '123');
await tester.tap(find.byKey(const Key('login')));
//await tester.pump();
await tester.pumpAndSettle();
//Login Success
expect(find.text('Login Success'), findsOneWidget);
expect(find.text('Logout'), findsOneWidget);
expect(find.text('Login'), findsNothing);
expect(find.text('thanh'), findsOne);
await tester.tap(find.text('Logout'));
await tester.pumpAndSettle();
await tester.pump();
expect(find.text('Login'), findsOne);
expect(find.text('Logout'), findsNothing);
expect(find.byType(SnackBar), findsOneWidget);
expect(find.text('Logout Success'), findsOneWidget);
});
}