252 lines
7.2 KiB
Swift
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
|
|
}
|
|
}
|