tutoriales.com

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.

Intermedio12 min de lectura17 views
Reportar error

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.

💡 Consejo: Adoptar una mentalidad de desarrollo guiado por pruebas (TDD) desde el principio de tu proyecto te ahorrará cientos de horas de depuración manual.

🛠️ 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
📌 Nota: `build_runner` es fundamental ya que se encarga de generar el código de los mocks que utilizaremos posteriormente con Mockito mediante anotaciones.

🧩 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.

Arquitectura de Pruebas con Mockito UI Widget ViewModel / BLoC Mockito Mock (Simula Respuesta) Servicio Clima (API Real) EN PRUEBAS Producción Llamada Interceptada Instancia Real

Nuestra estructura de carpetas ideal para este tipo de arquitectura limpia incluye:

Paso 1: Modelos y Servicios: Capa encargada de obtener los datos crudos de la API.
Paso 2: Lógica de Negocio: Capa donde procesamos los datos y manejamos el estado.
Paso 3: Vistas y Widgets: Capa visual que renderiza la información al usuario.

🧪 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
🔥 Importante: Cada vez que modifiques las clases que estás mockeando con anotaciones, debes volver a ejecutar el comando de `build_runner`.

🖼️ 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.

⚠️ Advertencia: Una cobertura del 100% no garantiza que tu aplicación esté libre de errores. Concéntrate en probar los caminos críticos y los casos límite (edge cases) de tu lógica de negocio.

💡 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 carpeta lib/.

❓ 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

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!