Pruebas en Swift: unit, integration, e2e de la A a la Z

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Por qué las pruebas son la base de una aplicación Swift confiable

El desarrollo para iOS y macOS requiere no solo una interfaz atractiva, sino también una lógica estable. Las pruebas en Swift no son una opción, sino una necesidad. Sin pruebas, incluso un cambio pequeño puede romper funcionalidades que han funcionado durante años. En este artículo analizaremos tres niveles de pruebas: unit (pruebas unitarias), integration (pruebas de integración) y e2e (pruebas de extremo a extremo). Aprenderá cómo configurarlas, escribirlas y ejecutarlas correctamente en las plataformas de Apple. Al final, encontrará consejos prácticos para organizar la arquitectura de pruebas.

Nivel 1: Pruebas unitarias — la base de todo

Las pruebas unitarias verifican funciones, métodos o clases individuales de forma aislada. En Swift, el framework principal es XCTest. Está integrado en Xcode, no requiere dependencias externas y es ideal para probar la lógica de negocio.

Cómo escribir tu primera prueba unitaria

Cree un nuevo Unit Test Target en Xcode (File → New → Target → Unit Testing Bundle). Luego agregue la clase de prueba:

import XCTest
@testable import MyApp

final class CalculatorTests: XCTestCase {
    
    var sut: Calculator!
    
    override func setUpWithError() throws {
        sut = Calculator()
    }
    
    override func tearDownWithError() throws {
        sut = nil
    }
    
    func testAddition() {
        // Given
        let a = 5
        let b = 3
        
        // When
        let result = sut.add(a, b)
        
        // Then
        XCTAssertEqual(result, 8, "La suma debe ser correcta")
    }
}

Mejores prácticas de pruebas unitarias

  • Aislamiento: cada prueba verifica solo una unidad de código. Use setUp y tearDown para restablecer el estado.
  • Nomenclatura: use el patrón test[Method]_[Condition]_[ExpectedResult].
  • Mocks y stubs: para dependencias externas (red, base de datos), use protocolos y sustitúyalos en las pruebas.
  • Cobertura: busque un 70-80% de cobertura de código, pero no persiga el 100% — pruebe las rutas críticas.

Ejemplo con mock

protocol NetworkServiceProtocol {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void)
}

class MockNetworkService: NetworkServiceProtocol {
    var shouldReturnError = false
    
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
        if shouldReturnError {
            completion(.failure(NSError(domain: "test", code: 1)))
        } else {
            completion(.success(Data()))
        }
    }
}

func testFetchDataSuccess() {
    let mock = MockNetworkService()
    let viewModel = ViewModel(networkService: mock)
    
    mock.shouldReturnError = false
    viewModel.loadData()
    XCTAssertTrue(viewModel.isDataLoaded)
}

Nivel 2: Pruebas de integración — verificación de la interacción

Las pruebas de integración verifican cómo diferentes módulos del sistema trabajan juntos: base de datos + red, UI + lógica de negocio, múltiples servicios. En Swift, se usa XCTest con dependencias reales (o en memoria).

Escenario típico: Core Data + Network

import XCTest
@testable import MyApp

class UserServiceIntegrationTests: XCTestCase {
    
    var coreDataStack: CoreDataStack!
    var networkService: NetworkService!
    var userService: UserService!
    
    override func setUp() {
        coreDataStack = CoreDataStack(inMemory: true) // en memoria para pruebas
        networkService = NetworkService()
        userService = UserService(coreDataStack: coreDataStack, networkService: networkService)
    }
    
    func testSaveUserFromNetwork() {
        // Creamos una expectativa para la solicitud asíncrona
        let expectation = self.expe                

Blogs

Book Recommendations