Files
PlantGuide/PlantGuideTests/Mocks/MockPlantClassificationService.swift
treyt 60189a5406 fix: issue #17 - upcoming tasks
Automated fix by Tony CI v3.
Refs #17

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-09 22:23:56 -05:00

252 lines
7.2 KiB
Swift

//
// 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
}
}