Una arquitectura Flutter testable con Riverpod
setState mezcla lógica e interfaz en la misma clase; InheritedWidget propaga bien el dato pero no dice nada de su creación ni de su reemplazo en pruebas. Riverpod responde a ambas: un contenedor de inyección de dependencias que produce valores reactivos, independientes del árbol de widgets.
Providers y notifiers
Provider expone un valor; un Notifier expone un valor mutable acompañado de la lógica que lo hace evolucionar. Con la generación de código ( riverpod_generator ), se anota una clase y el build() devuelve el estado inicial:
ref.watch dentro de un provider crea una dependencia : si apiClientProvider cambia, currentUser se recalcula automáticamente. Es el grafo de dependencias lo que reemplaza los setState manuales en cascada.
Separar la interfaz de la lógica
WidgetTester :
ref.watch(currentUserProvider) devuelve un AsyncValue , cuyo .when(data:, loading:, error:) cubre los tres estados sin un booleano `isLoading` vagabundo .
Inyección de dependencias y overrides en pruebas
InheritedWidget : cada provider es reemplazable al montar el ProviderContainer . En pruebas, se inyecta un cliente API falso sin tocar el código de producción:
addTearDown garantiza que se libere. La documentación de pruebas de Riverpod detalla los patrones de pump y de listeners.
Por qué no setState ni InheritedWidget
setState reconstruye todo el State y mantiene la lógica soldada a la interfaz — imposible de probar sin renderizar el widget. InheritedWidget comparte un valor pero obliga a escribir manualmente el updateShouldNotify , no gestiona ni la asincronía ni el reemplazo, y se escapa en cuanto se toca el BuildContext . Riverpod desplaza el estado fuera del árbol , lo hace perezoso, memoizado y auto-eliminado ( autoDispose ), y convierte el override en la vía normal de prueba.
Una buena arquitectura Flutter no se mide por el número de providers, sino por esto: ¿se puede probar la lógica sin montar nunca un widget ? Con Riverpod, la respuesta es sí por construcción.