import XCTest @testable import MangaReader /// Ejemplos de cómo escribir tests para el proyecto MangaReader /// Este archivo serve como guía de referencia para crear nuevos tests // MARK: - Ejemplo 1: Test Unitario Básico final class ExampleTests: XCTestCase { // MARK: - Ejemplo: Test simple de modelo func testEjemploModeloSimple() { // Arrange: Preparar los datos let manga = Manga( slug: "test", title: "Test Manga", description: "Desc", genres: ["Action"], status: "PUBLICANDOSE", url: "https://example.com" ) // Act: Ejecutar la acción (si es necesario) let displayTitle = manga.title // Assert: Verificar el resultado XCTAssertEqual(displayTitle, "Test Manga") XCTAssertEqual(manga.slug, "test") XCTAssertTrue(manga.genres.contains("Action")) } // MARK: - Ejemplo: Test con async/await func testEjemploAsync() async throws { // Arrange let storageService = StorageService.shared let image = createTestImage() // Act let url = try await storageService.saveImage( image, mangaSlug: "test", chapterNumber: 1, pageIndex: 0 ) // Assert XCTAssertTrue(FileManager.default.fileExists(atPath: url.path)) } // MARK: - Ejemplo: Test de error func testEjemploManejoDeError() async { // Arrange let storageService = StorageService.shared // Act & Assert do { _ = try await storageService.saveImage( UIImage(), // Imagen vacía mangaSlug: "test", chapterNumber: 1, pageIndex: 0 ) XCTFail("Debería haber lanzado un error") } catch { XCTAssertNotNil(error) } } // MARK: - Ejemplo: Test con helpers func testEjemploConHelpers() { // Usar TestDataFactory para crear datos de prueba let manga = TestDataFactory.createManga( slug: "mi-manga", title: "Mi Manga", genres: ["Action", "Fantasy"] ) // Usar AssertionHelpers AssertionHelpers.assertValidManga(manga) XCTAssertTrue(manga.genres.count == 2) } // MARK: - Ejemplo: Test de múltiples escenarios func testEjemploMultiplesEscenarios() { // Escenario 1: Manga publicado let publishedManga = TestDataFactory.createManga( status: "PUBLICANDOSE" ) XCTAssertEqual(publishedManga.displayStatus, "En publicación") // Escenario 2: Manga finalizado let finishedManga = TestDataFactory.createManga( status: "FINALIZADO" ) XCTAssertEqual(finishedManga.displayStatus, "Finalizado") // Escenario 3: Manga en pausa let pausedManga = TestDataFactory.createManga( status: "EN_PAUSA" ) XCTAssertEqual(pausedManga.displayStatus, "En pausa") } // MARK: - Ejemplo: Test de performance func testEjemploPerformance() { let manga = TestDataFactory.createManga() // Medir cuánto tarda en codificar 1000 veces measure { for _ in 0..<1000 { _ = try? JSONEncoder().encode(manga) } } } // MARK: - Ejemplo: Test con setup/teardown var storageService: StorageService! override func setUp() async throws { try await super.setUp() // Se ejecuta antes de cada test storageService = StorageService.shared StorageTestHelpers.clearAllStorage() } override func tearDown() async throws { // Se ejecuta después de cada test StorageTestHelpers.clearAllStorage() storageService = nil try await super.tearDown() } func testEjemploConSetup() { // El storageService ya está inicializado y limpio storageService.saveFavorite(mangaSlug: "test") XCTAssertTrue(storageService.isFavorite(mangaSlug: "test")) } // MARK: - Ejemplo: Test de integración func testEjemploIntegracion() async throws { // Simular flujo completo del usuario // 1. Usuario busca un manga let manga = TestDataFactory.createManga(slug: "tower-of-god") // 2. Usuario lo agrega a favoritos storageService.saveFavorite(mangaSlug: manga.slug) XCTAssertTrue(storageService.isFavorite(mangaSlug: manga.slug)) // 3. Usuario comienza a leer let progress = ReadingProgress( mangaSlug: manga.slug, chapterNumber: 1, pageNumber: 0, timestamp: Date() ) storageService.saveReadingProgress(progress) // 4. Verificar que todo se guardó correctamente let retrieved = storageService.getReadingProgress( mangaSlug: manga.slug, chapterNumber: 1 ) XCTAssertNotNil(retrieved) XCTAssertEqual(retrieved?.pageNumber, 0) } // MARK: - Ejemplo: Test concurrente func testEjemploConcurrencia() async throws { // Crear múltiples tareas concurrentes await withTaskGroup(of: Void.self) { group in for i in 0..<10 { group.addTask { let manga = TestDataFactory.createManga(slug: "manga-\(i)") self.storageService.saveFavorite(mangaSlug: manga.slug) } } } // Verificar que todas se guardaron let favorites = storageService.getFavorites() XCTAssertEqual(favorites.count, 10) } // MARK: - Ejemplo: Test con mock func testEjemploConMock() { // Mock de respuesta de JavaScript let mockResponse: [[String: Any]] = [ ["number": 1, "title": "Chapter 1", "url": "url1", "slug": "slug1"], ["number": 2, "title": "Chapter 2", "url": "url2", "slug": "slug2"] ] // Parsear respuesta mock let chapters = mockResponse.compactMap { dict -> Chapter? in guard let number = dict["number"] as? Int, let title = dict["title"] as? String, let url = dict["url"] as? String, let slug = dict["slug"] as? String else { return nil } return Chapter(number: number, title: title, url: url, slug: slug) } // Verificar parsing XCTAssertEqual(chapters.count, 2) XCTAssertEqual(chapters[0].number, 1) XCTAssertEqual(chapters[1].number, 2) } // MARK: - Ejemplo: Test de edge cases func testEjemploEdgeCases() { // Caso 1: String vacío let emptyManga = TestDataFactory.createManga(title: "") XCTAssertEqual(emptyManga.title, "") // Caso 2: Array vacío let noGenresManga = TestDataFactory.createManga(genres: []) XCTAssertTrue(noGenresManga.genres.isEmpty) // Caso 3: Valor negativo let chapter = TestDataFactory.createChapter(number: -1) XCTAssertEqual(chapter.number, -1) // Caso 4: Caracteres especiales let specialSlug = "manga-áéíóú-ñ-@#$" storageService.saveFavorite(mangaSlug: specialSlug) XCTAssertTrue(storageService.isFavorite(mangaSlug: specialSlug)) } // MARK: - Ejemplo: Test con assertions personalizadas func testEjemploAssertionsPersonalizadas() { let manga1 = TestDataFactory.createManga(slug: "test") let manga2 = TestDataFactory.createManga(slug: "test") // Usar assert personalizado XCTAssertEqual(manga1, manga2) XCTAssertEqual(manga1.hashValue, manga2.hashValue) // Verificar URL válida AssertionHelpers.assertValidURL(manga1.url) // Verificar manga válido AssertionHelpers.assertValidManga(manga1) } // MARK: - Helpers para ejemplos private func createTestImage() -> UIImage { let size = CGSize(width: 800, height: 1200) UIGraphicsBeginImageContext(size) let context = UIGraphicsGetCurrentContext() context?.setFillColor(UIColor.blue.cgColor) context?.fill(CGRect(origin: .zero, size: size)) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image! } } // MARK: - Plantillas de Tests /// Plantilla para test unitario simple /* func test[NombreFuncionalidad]_[Condición]_[ResultadoEsperado]() { // Arrange let [input] = [valor] // Act let result = [función](input) // Assert XCTAssertEqual(result, [esperado]) } */ /// Plantilla para test asíncrono /* func test[NombreFuncionalidad]_Async() async throws { // Arrange let [input] = [valor] // Act let result = try await [funciónAsync](input) // Assert XCTAssertNotNil(result) } */ /// Plantilla para test de error /* func test[NombreFuncionalidad]_ThrowsError() { // Arrange let [inputInvalido] = [valor] // Act & Assert XCTAssertThrowsError( try [función](inputInvalido) ) { error in XCTAssertEqual(error as! [TipoError], [errorEsperado]) } } */ /// Plantilla para test de performance /* func test[NombreFuncionalidad]_Performance() { let [data] = [crearDatosDePrueba] measure { _ = [función](data) } } */ /// Plantilla para test de integración /* func test[FlujoCompleto]_Integration() async throws { // Paso 1: [acción inicial] let [result1] = try await [función1]() // Paso 2: [acción siguiente] let [result2] = [función2](result1) // Paso 3: [verificación final] XCTAssertNotNil([resultFinal]) XCTAssertTrue([condición]) } */ // MARK: - Consejos para Escribir Tests /* ✅ BUENOS HÁBITOS: 1. Usa nombres descriptivos: - testSaveFavorite_AddsNewFavorite_WhenNotExists - testChapterProgress_ReturnsDouble_WhenSet 2. Un assert por test: - Separa en múltiples tests si hay varios asserts - Usa subtests si están relacionados 3. Arrange-Act-Assert: - Arrange: Prepara los datos - Act: Ejecuta la acción - Assert: Verifica el resultado 4. Tests independientes: - No dependen del orden de ejecución - Limpian después de sí mismos 5. Usa helpers: - TestDataFactory para crear objetos - AssertionHelpers para verificar condiciones ❌ MALOS HÁBITOS: 1. Tests con múltiples asserts no relacionados 2. Tests que dependen del orden 3. Tests que no limpian después 4. Tests con nombres ambiguos 5. Tests que llaman a APIs reales */ // MARK: - Referencias Rápidas /* COMUNES ASSERTIONS: - XCTAssertEqual(a, b) - Verifica igualdad - XCTAssertNotEqual(a, b) - Verifica desigualdad - XCTAssertTrue(condición) - Verifica true - XCTAssertFalse(condición) - Verifica false - XCTAssertNil(valor) - Verifica nil - XCTAssertNotNil(valor) - Verifica no nil - XCTAssertThrowsError(expr) - Verifica que lanza error - XCTAssertNoThrow(expr) - Verifica que NO lanza error ASYNC/AWAIT: - try await [función async] - Ejecutar función async - try await Task.sleep(...) - Esperar - await withTaskGroup {} - Tareas concurrentes SETUP/TEARDOWN: - override func setUp() - Antes de cada test - override func tearDown() - Después de cada test - override func setUpWithError() - Con errores - override func tearDownWithError() - Con errores PERFORMANCE: - measure { } - Medir bloques de código - measure(metrics: ...) { } - Métricas específicas MOCKS: - TestDataFactory - Crear objetos de prueba - ImageTestHelpers - Crear imágenes - FileSystemTestHelpers - Operaciones de archivos */