Stub (stub, doble de prueba) — un objeto de prueba que devuelve respuestas predefinidas a llamadas de métodos en lugar de una implementación real. En el desarrollo móvil, los stubs aíslan el módulo probado de las solicitudes de red, bases de datos y sistemas de archivos, permitiendo verificar la lógica sin configuración del entorno. A diferencia de mock, stub no verifica el comportamiento — solo proporciona datos. Más detalles en el artículo de Martin Fowler sobre test doubles.
Puntos clave
Stub es un objeto doble de prueba que reemplaza una dependencia real en un test y devuelve valores predefinidos para llamadas específicas. El término fue introducido en la clasificación de Gerard Meszaros (2007) en el libro «xUnit Test Patterns». Stub pertenece a la categoría de test doubles — objetos que sustituyen componentes reales durante las pruebas. El propósito principal de un stub es proporcionar a la unidad bajo prueba datos predecibles, eliminando la incertidumbre de los sistemas externos.
Cómo funciona — el test configura el stub antes de la ejecución: «cuando se llame al método getUsers(), devuelve esta lista de usuarios». Stub no contiene lógica de negocio, no verifica el orden de las llamadas ni registra el historial. Simplemente se coloca en lugar del componente real y devuelve lo que se le indicó. En el contexto de pruebas de Android, esto significa: el cliente OkHttp no hace una solicitud real al servidor, sino que recibe una respuesta de MockWebServer configurado como stub.
Cuándo usarlos — los stubs son óptimos para probar la capa de UI (ViewModel, Presenter) y la lógica de negocio (UseCase, Interactor), donde se necesita verificar la reacción a datos específicos: lista vacía, el servidor devolvió error 500, token expirado. Cualquier caso donde el test requiera un estado de entrada específico es tarea para stub. Cada escenario de prueba tiene su propia configuración de stub, lo que hace que las pruebas sean legibles y predecibles.
Gerard Meszaros (2007) en el libro «xUnit Test Patterns» identificó cinco tipos de test doubles: dummy, stub, spy, mock, fake. Cada tipo cumple su función. Dummy — se pasa pero no se usa. Stub — devuelve datos. Spy — registra llamadas. Mock — verifica comportamiento. Fake — contiene lógica simplificada. Comprender esta clasificación ayuda al desarrollador a elegir la herramienta correcta para cada escenario de prueba.
Solicitudes de red — el escenario más común para usar stubs. La aplicación hace llamadas HTTP a la API, y el test necesita verificar la reacción a diferentes respuestas: JSON exitoso, error 401 (no autorizado), tiempo de espera agotado, array vacío. MockWebServer (OkHttp) en Android y URLProtocol (iOS) actúan como stubs, devolviendo respuestas HTTP predefinidas sin conexión real al servidor. Esto acelera las pruebas de segundos a milisegundos.
Base de datos — Room (Android) y CoreData (iOS) tienen variantes en memoria, pero su configuración aún requiere tiempo. Stub en lugar del repositorio devuelve listas de Entity preparadas previamente sin tocar la BD. Esto es especialmente efectivo para probar ViewModel, donde se necesita verificar ordenamiento, filtrado o transformación de datos. La prueba se ejecuta en milisegundos independientemente del volumen de datos.
Servicios del sistema — LocationManager, SensorManager, SharedPreferences requieren un dispositivo real o emulador. Stub para LocationProvider devuelve coordenadas predefinidas, para SensorManager — valores fijos del acelerómetro. En iOS, el equivalente es CLLocationManager con una implementación de delegado de prueba. Sin stubs, estas pruebas requieren un dispositivo físico con condiciones específicas.
Sistema de archivos y caché — carga de imágenes, almacenamiento en caché de respuestas, trabajo con archivos de configuración — todas estas operaciones dependen del estado del disco. Stub para FileManager o ImageCache devuelve éxito/error sin leer archivos reales. Esto elimina fallos falsos en pruebas debido a discrepancias de rutas o permisos de acceso en diferentes máquinas de desarrollo.
División de responsabilidades — tres tipos de test doubles resuelven diferentes tareas. Stub: «dame datos». Mock: «verifica que fui llamado». Fake: «funciono como el real, solo que más simple». La diferencia es crítica para la legibilidad de las pruebas: si un test usa mock donde se necesita stub, está sobrecargado de llamadas verify no relacionadas con el escenario probado.
| Característica | Stub | Mock | Fake |
|---|---|---|---|
| Propósito | Proporcionar datos | Verificar interacción | Implementación simplificada |
| Lógica | No | No | Sí (pero simplificada) |
| Verificación | No | Sí (verify) | Indirecta (mediante estado) |
| Flexibilidad | Baja — respuestas fijas | Media | Alta — la lógica se adapta |
| Velocidad | Máxima | Alta | Media |
| Ejemplo | MockWebServer devuelve JSON | Mockito.verify(repository).save() | InMemoryRepository con HashMap |
Regla práctica — si el test verifica qué datos recibió el componente bajo prueba — usa stub. Si el test verifica si el componente llamó al método de la dependencia con los argumentos correctos — usa mock. Si simplemente quieres reemplazar una BD por una tabla hash — eso es fake. Mezclar tipos en un mismo test lo vuelve frágil: al cambiar la implementación, tendrás que reescribir tanto la lógica de stub como de verify.
Stub con verify — un error común donde el desarrollador configura un stub y luego añade verify(stub).method(). Stub por definición no debe ser verificado — para ello está mock. Si necesitas comprobar que se llamó a un método con argumentos específicos, usa Mockito.mock() en lugar de Mockito.stub(). Esta separación mantiene la intención del test clara para otros desarrolladores.
MockWebServer — una librería de OkHttp para crear stubs HTTP en Android y JVM. Inicia un servidor HTTP local en un puerto especificado que intercepta las solicitudes del cliente OkHttp y devuelve respuestas predefinidas. La configuración toma tres líneas: crear el servidor, enqueue la respuesta, iniciarlo. El test puede encolar secuencialmente múltiples respuestas para escenarios con paginación o reintentos.
class UserRepositoryTest {
private val server = MockWebServer()
fun setup() {
server.start(8080)
val client = OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.build()
}
fun test_user_list_success() {
val json = "[{\"id\":1,\"name\":\"Alice\"}]"
server.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200)
)
val result = repository.getUsers()
assertEquals(1, result.size)
}
fun teardown() {
server.shutdown()
}
}
MockK — una alternativa a Mockito para Kotlin con soporte de primera clase para corrutinas, funciones de extensión y clases selladas. Los stubs en MockK se crean mediante coEvery (para funciones suspend) y every (para funciones regulares). A diferencia de MockWebServer, MockK stub métodos de dependencia individuales en lugar de toda la capa HTTP. Esto es útil para pruebas unitarias de UseCase o Interactor, donde las dependencias son abstracciones de repositorios.
interface UserRepository {
suspend fun getUsers(): List<User>
}
class GetUsersUseCaseTest {
private val repo = mockk<UserRepository>()
private val useCase = GetUsersUseCase(repo)
fun test_empty_list() = runTest {
coEvery { repo.getUsers() } returns emptyList()
val result = useCase.invoke()
assertTrue(result.isEmpty())
coVerify(exactly = 1) { repo.getUsers() }
}
}
Mejores prácticas — para pruebas de integración usa MockWebServer (intercepta HTTP real), para pruebas unitarias usa MockK (stub interfaces). No stubees lo que no estás probando: si el test verifica el Repository, no stubees el cliente OkHttp dentro de él — usa un MockWebServer real a nivel HTTP. Esta regla mantiene las pruebas relevantes y reduce la fragilidad durante la refactorización.
Protocolos de Swift como stubs — en el enfoque nativo de iOS, el stub se implementa mediante la sustitución de una estructura de prueba que cumple con el protocolo de la dependencia. En lugar de un NetworkService real, el test recibe un StubNetworkService que devuelve datos fijos. Swift es un lenguaje con tipado estático, por lo que el stub debe cumplir con el mismo protocolo que el servicio real. El compilador garantiza que el stub implemente todos los métodos requeridos.
protocol NetworkServiceProtocol {
func fetchUsers() async throws -> [User]
}
struct StubNetworkService: NetworkServiceProtocol {
let result: Result<[User], Error>
func fetchUsers() async throws -> [User] {
try result.get()
}
}
final class UsersViewModelTests: XCTestCase {
func test_success_state() async {
let stub = StubNetworkService(
result: .success([User(name: "Alice")])
)
let vm = UsersViewModel(service: stub)
await vm.load()
XCTAssertEqual(vm.users.count, 1)
}
}
OCMock para Objective-C — una librería para crear stubs y mocks en proyectos iOS legacy. OCMock admite métodos stub con argumentos y valores de retorno. Los proyectos modernos en Swift prefieren un enfoque basado en protocolos con stubs manuales — esto da control sobre cada método y no requiere dependencias externas. OCMock sigue siendo una opción para proyectos donde protocolizar todas las dependencias no es económicamente viable.
URLProtocol para stubs HTTP — un mecanismo del sistema en iOS para interceptar solicitudes de red mediante una subclase de URLProtocol. El test registra un URLProtocol personalizado que intercepta URLSession y devuelve respuestas stub. La ventaja sobre los stubs manuales: no es necesario cambiar la arquitectura de la aplicación — URLSession sigue siendo real, pero los datos se sustituyen a nivel de protocolo. La desventaja: es más difícil de depurar que un servicio stub explícito.
Preguntas frecuentes
Stub devuelve datos predefinidos y no verifica si ocurrió una llamada. Mock adicionalmente verifica que el método fue llamado con los argumentos correctos (verify). Stub responde a la pregunta «qué devolver», Mock responde a «se hizo la llamada». Usa stub para verificación de estado, mock para verificación de interacción.
Fake es necesario cuando el test requiere una implementación funcional (aunque simplificada) — por ejemplo, una base de datos en memoria en lugar de Room. Stub es adecuado para escenarios únicos con datos predefinidos. Si repites el mismo stub en 10 pruebas — probablemente necesitas un Fake. Fake reduce la duplicación porque la lógica vive en una sola clase.
En Android — MockK para objetos de Kotlin (object) soporta mockkObject(), incluyendo métodos estáticos de clases Java mediante mockkStatic(). En iOS — los métodos estáticos de Swift no se pueden stubear directamente; usa protocolos e DI para reemplazar una llamada static por un método de instancia de un protocolo. Los stubs estáticos son deuda técnica y deben evitarse en código nuevo.
Usa MockWebServer (OkHttp) — funciona como un servidor HTTP local que encola respuestas. Para Retrofit, simplemente reemplaza la URL base por localhost:8080. Para Ktor, usa MockEngine — un mecanismo integrado para sustituir HttpStatement. Ambos enfoques funcionan sin internet real y dan control total sobre el código de estado, el cuerpo y los encabezados de la respuesta.
Spy envuelve un objeto real y registra llamadas, mientras que Stub reemplaza completamente el objeto con respuestas fijas. Spy permite el uso parcial de la implementación real (otros métodos funcionan como están), mientras que stub no. Si necesitas verificar que se llamó a un método pero parte de la lógica debe ejecutarse — usa spy, no stub.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también