Para mockar um Method Channel
, precisamos imitar o seu comportamento. Para conseguir isso podemos utilizar o TestDefaultBinaryMessenger.setMockMessageHandler
.
O método setMockMessageHandle
irá substituir o callback
fornecido para o MethodChannel
pelo handler
especificado durante o teste. Dessa forma, temos controle sobre a dependência durante o contexto do teste.
Contexto
Imagine que você precisa realizar a migração parcial de um fluxo para Flutter, e nessa execução, você precisará passar dados da tela nativa para o Flutter. Como podemos garantir esse comportamento?
Nossa tela
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const UserScreen(),
);
}
}
class UserScreen extends StatelessWidget {
const UserScreen({super.key});
static const MethodChannel _channel = MethodChannel('br.com.my_package/user_data_provider');
Future<Map<String, dynamic>> _getUserData() async {
try{
final String result = await _channel.invokeMethod('getUserData');
final Map<String, dynamic> data = jsonDecode(result);
return data;
} catch(e) {
return {};
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Teste'),
),
body: FutureBuilder(
future: _getUserData(),
builder: (context, snapshot) {
print('Oi $snapshot');
if(snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
else if(snapshot.hasError) {
return const Text('Erro!');
}
else if(!snapshot.hasData) {
return const Text('Não há dados!');
}
else {
final userData = snapshot.data!;
return Column(
children: [
Text('Nome: ${userData['name']}'),
Text('Idade: ${userData['age']}')
],
);
}
},
),
);
}
}
Nosso teste
import 'package:app/main.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('Testando Method Channel', () {
const stubbedMethodChannel = MethodChannel('br.com.my_package/user_data_provider');
TestWidgetsFlutterBinding.ensureInitialized();
setUp((){
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
stubbedMethodChannel,
(methodCall) async {
if(methodCall.method == 'getUserData') {
const userResponse = '''
{
"name": "Caique",
"age": 29
}
''';
return userResponse;
}
return null;
}
);
});
testWidgets('Cenário 1 - Emissão básica', (tester) async {
await tester.pumpWidget(const MyApp());
expect(find.byType(UserScreen), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
expect(find.text('Nome: Caique'), findsOneWidget);
expect(find.text('Idade: 29'), findsOneWidget);
});
});
}
Conclusão
Em nosso exemplo, vimos como assumir o controle do MethodChannel
durante o contexto do nosso teste. Para isso, utimizamos a função setMockMethodCallHandler
, que permite substituirmos o callback
do Channel
pelo que gostaríamos.
Isso nos possibilita executar os cenários que nossa aplicação poderia ter sem mesmo termos de compilar o código nativo, o que torna o ciclo de feedback de desenvolvimento menor, possibilitando entregar com mais qualidade e em menos tempo.