Flutter: Pruebas Unitarias y de Widgets Eficientes con Mockito
Domina el arte del testing en Flutter creando pruebas unitarias para tu lógica de negocio y pruebas de widgets para tus interfaces de usuario, simulando dependencias con Mockito.
Introducción al Testing en Flutter 📱
El desarrollo de aplicaciones móviles modernas exige un alto estándar de calidad. A medida que una aplicación en Flutter crece, mantener la estabilidad del código se vuelve un desafío crítico. Aquí es donde entran en juego las pruebas automatizadas. Escribir pruebas no solo previene errores en producción, sino que también sirve como una excelente documentación de cómo debe comportarse tu código.
En este tutorial completo, exploraremos cómo implementar pruebas unitarias y de widgets utilizando el framework nativo flutter_test combinado con la librería Mockito para simular dependencias externas como servicios de red o bases de datos locales.
🛠️ Configuración del Entorno y Dependencias
Antes de escribir nuestro primer código de prueba, necesitamos configurar nuestro archivo pubspec.yaml para incluir las herramientas de testing necesarias. Flutter incluye flutter_test por defecto, pero necesitaremos agregar mockito y build_runner para generar nuestros mocks.
Abre tu archivo pubspec.yaml y añade las siguientes dependencias en la sección de dev_dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.4.4
build_runner: ^2.4.8
Una vez añadidas las dependencias, ejecuta el siguiente comando en tu terminal para instalarlas en tu proyecto:
flutter pub get
🧩 Arquitectura de la Aplicación de Ejemplo
Para que este tutorial sea completamente práctico, trabajaremos sobre un escenario real: una aplicación que consume una API de clima y muestra los resultados en una interfaz gráfica.
Nuestra estructura de carpetas ideal para este tipo de arquitectura limpia incluye:
🧪 Pruebas Unitarias con Mockito
Las pruebas unitarias se centran en verificar el correcto funcionamiento de una unidad individual de código, como una función, un método o una clase, de forma aislada.
Creando el Servicio a Probar
Imaginemos que tenemos un servicio llamado WeatherService que se conecta a una API externa:
import 'http/http.dart' as http;
import 'dart:convert';
class WeatherService {
final http.Client client;
WeatherService({required this.client});
Future<String> fetchWeather(String city) async {
final response = await client.get(Uri.parse('https://api.weather.com/v1/$city'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['temperature'];
} else {
throw Exception('Error al cargar el clima');
}
}
}
Generando Mocks con Anotaciones
Para probar este servicio sin hacer peticiones reales a internet (lo cual haría nuestras pruebas lentas e inestables), utilizamos Mockito para crear un cliente HTTP falso.
Crea un archivo llamado weather_service_test.dart dentro de la carpeta test/:
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
// Generamos la clase MockClient basada en http.Client
@GenerateMocks([http.Client])
import 'weather_service_test.mocks.dart';
void main() {
late WeatherService weatherService;
late MockClient mockClient;
setUp(() {
mockClient = MockClient();
weatherService = WeatherService(client: mockClient);
});
group('WeatherService Tests', () {
test('retorna temperatura cuando la respuesta es exitosa (200)', () async {
// Configuramos el comportamiento del mock
when(mockClient.get(Uri.parse('https://api.weather.com/v1/Madrid')))
.thenAnswer((_) async => http.Response('{"temperature": "22°C"}', 200));
final temperature = await weatherService.fetchWeather('Madrid');
expect(temperature, '22°C');
});
test('lanza una excepcion cuando la respuesta falla', () async {
when(mockClient.get(Uri.parse('https://api.weather.com/v1/Madrid')))
.thenAnswer((_) async => http.Response('Error', 404));
expect(() => weatherService.fetchWeather('Madrid'), throwsException);
});
});
}
Para generar el archivo .mocks.dart requerido por Mockito, debes ejecutar en tu terminal:
flutter pub run build_runner build --delete-conflicting-outputs
🖼️ Pruebas de Widgets (Widget Testing)
Las pruebas de widgets en Flutter permiten probar componentes visuales de la interfaz de usuario. Con estas pruebas podemos verificar que los widgets se renderizan correctamente, responden a gestos del usuario y muestran los estados esperados.
Diseñando el Widget de Clima
Supongamos que tenemos un widget simple que muestra la temperatura o un indicador de carga:
import 'package:flutter/material.dart';
class WeatherScreen extends StatelessWidget {
final String temperature;
final bool isLoading;
const WeatherScreen({Key? key, required this.temperature, required this.isLoading}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Clima Actual')),
body: Center(
child: isLoading
? const CircularProgressIndicator()
: Text('Temperatura: $temperature', style: const TextStyle(fontSize: 24)),
),
),
);
}
}
Escribiendo la Prueba del Widget
Ahora escribiremos una prueba para verificar que el indicador de carga se muestra cuando isLoading es verdadero, y que la temperatura se muestra correctamente cuando el estado cambia.
Crea un archivo llamado weather_screen_test.dart en la carpeta test/:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Muestra CircularProgressIndicator cuando esta cargando', (WidgetTester tester) async {
// Construimos nuestro widget dentro del entorno de prueba
await tester.pumpWidget(const WeatherScreen(temperature: '', isLoading: true));
// Verificamos que el indicador de carga este presente
expect(find.byType(CircularProgressIndicator), findsOneWidget);
expect(find.byType(Text), findsOneWidget); // Titulo del AppBar
});
testWidgets('Muestra la temperatura correctamente cuando los datos estan listos', (WidgetTester tester) async {
await tester.pumpWidget(const WeatherScreen(temperature: '25°C', isLoading: false));
// Verificamos que el texto de carga ya no este presente y aparezca la temperatura
expect(find.byType(CircularProgressIndicator), findsNothing);
expect(find.text('Temperatura: 25°C'), findsOneWidget);
});
}
🚀 Ejecutando y Analizando las Pruebas
Una vez que hayas escrito tus pruebas unitarias y de widgets, es momento de ejecutarlas. Puedes hacerlo directamente desde tu entorno de desarrollo favorito (como VS Code o Android Studio) o utilizando la línea de comandos:
flutter test
Si deseas verificar el porcentaje de cobertura de código de tus pruebas, puedes ejecutar:
flutter test --coverage
Esto generará un archivo de reportes en la carpeta coverage/ que podrás visualizar con herramientas gráficas o extensiones de tu editor.
💡 Buenas Prácticas para un Testing Exitoso
Para mantener tus pruebas limpias, rápidas y fáciles de mantener a lo largo del ciclo de vida del proyecto, ten en cuenta las siguientes recomendaciones:
- Aislamiento absoluto: Cada prueba debe ser independiente de las demás. Nunca dependas del resultado de una prueba anterior.
- Nombres descriptivos: Utiliza nombres claros para tus tests que describan qué se está probando y cuál es el resultado esperado.
- Evita llamadas reales a redes: Utiliza siempre mocks o fakes para servicios externos, bases de datos y APIs.
- Organización: Mantén la estructura de la carpeta
test/reflejando exactamente la misma estructura de tu carpetalib/.
❓ Preguntas Frecuentes (FAQ)
¿Cuál es la diferencia principal entre una prueba unitaria y una prueba de widgets?
Las pruebas unitarias evalúan funciones, métodos o clases de forma totalmente aislada sin renderizar ningún componente visual. Las pruebas de widgets evalúan la interfaz de usuario, permitiendo interactuar con los componentes gráficos y verificar su renderizado en pantalla.¿Por qué Mockito requiere ejecutar build_runner?
Mockito en Dart utiliza generación de código en tiempo de compilación para crear las clases simuladas (mocks) seguras de tipos basándose en anotaciones, lo que evita errores en tiempo de ejecución.¿Puedo hacer pruebas de integración con flutter_test?
No exactamente con `flutter_test` tradicional para pruebas unitarias/widgets. Para pruebas de integración completas que corren en dispositivos reales o emuladores, debes utilizar el paquete `integration_test`.Conclusión 🎉
¡Felicidades! Has aprendido los fundamentos esenciales para implementar pruebas unitarias y de widgets en tus aplicaciones Flutter utilizando Mockito. Con estas herramientas, podrás refactorizar tu código con confianza, detectar errores antes de que lleguen a los usuarios y asegurar una experiencia móvil robusta y profesional.
Sigue practicando creando tus propios mocks y aumentando la cobertura de pruebas en tus proyectos actuales.
Tutoriales relacionados
- Flutter: Creando un Cliente de Chat en Tiempo Real con WebSockets y Providerintermediate20 min
- Flutter al Detalle: Animaciones Implícitas y Explícitas para Interfaces de Usuario Fluidas y Atractivasintermediate15 min
- Flutter para Principiantes: Construyendo una Lista de Tareas Interactivas y Persistentes con Isar DBbeginner30 min
- Flutter: Integrando Mapas Interactivos con Google Maps y Geolocalización en tu Appintermediate25 min
- Flutter: Integración de Pagos con Stripe para E-commerce Móvilintermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!