// // MockPlantClassificationService.swift // PlantGuideTests // // Mock implementations for ML-related services for unit testing. // Provides configurable behavior and call tracking for verification. // import Foundation import CoreGraphics import UIKit @testable import PlantGuide // MARK: - MockPlantClassificationService /// Mock implementation of PlantClassificationServiceProtocol for testing final actor MockPlantClassificationService: PlantClassificationServiceProtocol { // MARK: - Call Tracking private(set) var classifyCallCount = 0 // MARK: - Error Configuration var shouldThrowOnClassify = false var errorToThrow: Error = PlantClassificationError.modelLoadFailed // MARK: - Return Value Configuration var predictionsToReturn: [PlantPrediction] = [] // MARK: - Captured Values private(set) var lastClassifiedImage: CGImage? // MARK: - PlantClassificationServiceProtocol func classify(image: CGImage) async throws -> [PlantPrediction] { classifyCallCount += 1 lastClassifiedImage = image if shouldThrowOnClassify { throw errorToThrow } // Return configured predictions or empty array return predictionsToReturn } // MARK: - Helper Methods /// Resets all state for clean test setup func reset() { classifyCallCount = 0 shouldThrowOnClassify = false errorToThrow = PlantClassificationError.modelLoadFailed predictionsToReturn = [] lastClassifiedImage = nil } /// Configures the mock to return predictions for common test plants func configureMockPredictions(_ predictions: [PlantPrediction]) { predictionsToReturn = predictions } /// Creates a default set of mock predictions func configureDefaultPredictions() { predictionsToReturn = [ PlantPrediction( speciesIndex: 0, confidence: 0.92, scientificName: "Monstera deliciosa", commonNames: ["Swiss Cheese Plant", "Monstera"] ), PlantPrediction( speciesIndex: 1, confidence: 0.75, scientificName: "Philodendron bipinnatifidum", commonNames: ["Split Leaf Philodendron"] ), PlantPrediction( speciesIndex: 2, confidence: 0.45, scientificName: "Epipremnum aureum", commonNames: ["Pothos", "Devil's Ivy"] ) ] } /// Configures throw behavior from outside the actor func setThrowBehavior(shouldThrow: Bool, error: Error) { shouldThrowOnClassify = shouldThrow errorToThrow = error } /// Configures low confidence predictions for testing fallback behavior func configureLowConfidencePredictions() { predictionsToReturn = [ PlantPrediction( speciesIndex: 0, confidence: 0.35, scientificName: "Unknown plant", commonNames: ["Unidentified"] ) ] } } // MARK: - MockImagePreprocessor /// Mock implementation of ImagePreprocessorProtocol for testing struct MockImagePreprocessor: ImagePreprocessorProtocol, Sendable { // MARK: - Configuration var shouldThrow = false var errorToThrow: Error = ImagePreprocessorError.cgImageCreationFailed // MARK: - Return Value Configuration var imageToReturn: CGImage? // MARK: - ImagePreprocessorProtocol func preprocess(_ image: UIImage) async throws -> CGImage { if shouldThrow { throw errorToThrow } // Return configured image or create one from the input if let configuredImage = imageToReturn { return configuredImage } guard let cgImage = image.cgImage else { throw ImagePreprocessorError.cgImageCreationFailed } return cgImage } } // MARK: - MockIdentifyPlantUseCase /// Mock implementation of IdentifyPlantUseCaseProtocol for testing final class MockIdentifyPlantUseCase: IdentifyPlantUseCaseProtocol, @unchecked Sendable { // MARK: - Configuration var shouldThrow = false var errorToThrow: Error = IdentifyPlantOnDeviceUseCaseError.noMatchesFound // MARK: - Return Value Configuration var predictionsToReturn: [ViewPlantPrediction] = [] // MARK: - IdentifyPlantUseCaseProtocol func execute(image: UIImage) async throws -> [ViewPlantPrediction] { if shouldThrow { throw errorToThrow } return predictionsToReturn } // MARK: - Factory Methods /// Creates a mock that returns high-confidence predictions static func withHighConfidencePredictions() -> MockIdentifyPlantUseCase { let mock = MockIdentifyPlantUseCase() mock.predictionsToReturn = [ ViewPlantPrediction( id: UUID(), speciesName: "Monstera deliciosa", commonName: "Swiss Cheese Plant", confidence: 0.95 ) ] return mock } /// Creates a mock that returns low-confidence predictions static func withLowConfidencePredictions() -> MockIdentifyPlantUseCase { let mock = MockIdentifyPlantUseCase() mock.predictionsToReturn = [ ViewPlantPrediction( id: UUID(), speciesName: "Unknown", commonName: nil, confidence: 0.35 ) ] return mock } /// Creates a mock that throws an error static func withError(_ error: Error = IdentifyPlantOnDeviceUseCaseError.noMatchesFound) -> MockIdentifyPlantUseCase { let mock = MockIdentifyPlantUseCase() mock.shouldThrow = true mock.errorToThrow = error return mock } } // MARK: - MockIdentifyPlantOnlineUseCase /// Mock implementation of IdentifyPlantOnlineUseCaseProtocol for testing final class MockIdentifyPlantOnlineUseCase: IdentifyPlantOnlineUseCaseProtocol, @unchecked Sendable { // MARK: - Configuration var shouldThrow = false var errorToThrow: Error = IdentifyPlantOnlineUseCaseError.noMatchesFound // MARK: - Return Value Configuration var predictionsToReturn: [ViewPlantPrediction] = [] // MARK: - IdentifyPlantOnlineUseCaseProtocol func execute(image: UIImage) async throws -> [ViewPlantPrediction] { if shouldThrow { throw errorToThrow } return predictionsToReturn } // MARK: - Factory Methods /// Creates a mock that returns API predictions static func withPredictions() -> MockIdentifyPlantOnlineUseCase { let mock = MockIdentifyPlantOnlineUseCase() mock.predictionsToReturn = [ ViewPlantPrediction( id: UUID(), speciesName: "Monstera deliciosa", commonName: "Swiss Cheese Plant", confidence: 0.98 ) ] return mock } /// Creates a mock that throws an error static func withError(_ error: Error = IdentifyPlantOnlineUseCaseError.noMatchesFound) -> MockIdentifyPlantOnlineUseCase { let mock = MockIdentifyPlantOnlineUseCase() mock.shouldThrow = true mock.errorToThrow = error return mock } }