Files
PlantGuide/PlantGuideTests/NetworkMonitorTests.swift
Trey t 136dfbae33 Add PlantGuide iOS app with plant identification and care management
- Implement camera capture and plant identification workflow
- Add Core Data persistence for plants, care schedules, and cached API data
- Create collection view with grid/list layouts and filtering
- Build plant detail views with care information display
- Integrate Trefle botanical API for plant care data
- Add local image storage for captured plant photos
- Implement dependency injection container for testability
- Include accessibility support throughout the app

Bug fixes in this commit:
- Fix Trefle API decoding by removing duplicate CodingKeys
- Fix LocalCachedImage to load from correct PlantImages directory
- Set dateAdded when saving plants for proper collection sorting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:18:01 -06:00

361 lines
9.4 KiB
Swift

//
// NetworkMonitorTests.swift
// PlantGuideTests
//
// Unit tests for NetworkMonitor - the network connectivity monitoring service
// that tracks network status and connection type changes.
//
import XCTest
import Network
@testable import PlantGuide
// MARK: - NetworkMonitorTests
final class NetworkMonitorTests: XCTestCase {
// MARK: - Properties
private var sut: NetworkMonitor!
// MARK: - Test Lifecycle
override func setUp() {
super.setUp()
sut = NetworkMonitor()
}
override func tearDown() {
sut?.stopMonitoring()
sut = nil
super.tearDown()
}
// MARK: - Initialization Tests
func testInit_CreatesMonitor() {
XCTAssertNotNil(sut)
}
func testInit_StartsMonitoringAutomatically() {
// NetworkMonitor starts monitoring in init
// Just verify it doesn't crash and is created
XCTAssertNotNil(sut)
}
// MARK: - Connection Status Tests
func testIsConnected_InitialValue_IsBool() {
// Just verify the property is accessible and is a boolean
let isConnected = sut.isConnected
XCTAssertTrue(isConnected || !isConnected) // Always true - just checks type
}
func testConnectionType_InitialValue_IsConnectionType() {
// Verify the property is accessible and has a valid value
let connectionType = sut.connectionType
let validTypes: [ConnectionType] = [.wifi, .cellular, .ethernet, .unknown]
XCTAssertTrue(validTypes.contains(connectionType))
}
// MARK: - Start/Stop Monitoring Tests
func testStartMonitoring_WhenAlreadyStarted_DoesNotCrash() {
// Given - Already started in init
// When - Start again
sut.startMonitoring()
// Then - Should not crash
XCTAssertNotNil(sut)
}
func testStopMonitoring_WhenMonitoring_StopsSuccessfully() {
// Given - Already started in init
// When
sut.stopMonitoring()
// Then - Should not crash
XCTAssertNotNil(sut)
}
func testStopMonitoring_WhenAlreadyStopped_DoesNotCrash() {
// Given
sut.stopMonitoring()
// When - Stop again
sut.stopMonitoring()
// Then - Should not crash
XCTAssertNotNil(sut)
}
func testStartMonitoring_AfterStop_RestartsSuccessfully() {
// Given
sut.stopMonitoring()
// When
sut.startMonitoring()
// Then - Should not crash
XCTAssertNotNil(sut)
}
// MARK: - ConnectionType Tests
func testConnectionType_WiFi_HasCorrectRawValue() {
XCTAssertEqual(ConnectionType.wifi.rawValue, "wifi")
}
func testConnectionType_Cellular_HasCorrectRawValue() {
XCTAssertEqual(ConnectionType.cellular.rawValue, "cellular")
}
func testConnectionType_Ethernet_HasCorrectRawValue() {
XCTAssertEqual(ConnectionType.ethernet.rawValue, "ethernet")
}
func testConnectionType_Unknown_HasCorrectRawValue() {
XCTAssertEqual(ConnectionType.unknown.rawValue, "unknown")
}
// MARK: - Thread Safety Tests
func testConcurrentAccess_DoesNotCrash() {
// Given
let expectation = XCTestExpectation(description: "Concurrent access completes")
expectation.expectedFulfillmentCount = 100
// When - Access from multiple threads
for _ in 0..<100 {
DispatchQueue.global().async {
_ = self.sut.isConnected
_ = self.sut.connectionType
expectation.fulfill()
}
}
// Then
wait(for: [expectation], timeout: 5.0)
}
func testConcurrentStartStop_DoesNotCrash() {
// Given
let expectation = XCTestExpectation(description: "Concurrent start/stop completes")
expectation.expectedFulfillmentCount = 20
// When - Start and stop from multiple threads
for i in 0..<20 {
DispatchQueue.global().async {
if i % 2 == 0 {
self.sut.startMonitoring()
} else {
self.sut.stopMonitoring()
}
expectation.fulfill()
}
}
// Then
wait(for: [expectation], timeout: 5.0)
}
// MARK: - Lifecycle Tests
func testDeinit_StopsMonitoring() {
// Given
var monitor: NetworkMonitor? = NetworkMonitor()
XCTAssertNotNil(monitor)
// When
monitor = nil
// Then - Should not crash (deinit calls stopMonitoring)
XCTAssertNil(monitor)
}
func testMultipleInstances_DoNotInterfere() {
// Given
let monitor1 = NetworkMonitor()
let monitor2 = NetworkMonitor()
// When
monitor1.stopMonitoring()
// Then - monitor2 should still work
XCTAssertNotNil(monitor2)
let isConnected = monitor2.isConnected
XCTAssertTrue(isConnected || !isConnected) // Just verify access works
}
// MARK: - Observable Property Tests
func testIsConnected_CanBeObserved() {
// Given
let expectation = XCTestExpectation(description: "Property can be read")
// When
DispatchQueue.main.async {
_ = self.sut.isConnected
expectation.fulfill()
}
// Then
wait(for: [expectation], timeout: 1.0)
}
func testConnectionType_CanBeObserved() {
// Given
let expectation = XCTestExpectation(description: "Property can be read")
// When
DispatchQueue.main.async {
_ = self.sut.connectionType
expectation.fulfill()
}
// Then
wait(for: [expectation], timeout: 1.0)
}
// MARK: - Edge Cases
func testRapidStartStop_DoesNotCrash() {
// Rapidly toggle monitoring
for _ in 0..<50 {
sut.startMonitoring()
sut.stopMonitoring()
}
// Should not crash
XCTAssertNotNil(sut)
}
func testNewInstance_AfterOldOneDeallocated_Works() {
// Given
var monitor: NetworkMonitor? = NetworkMonitor()
monitor?.stopMonitoring()
monitor = nil
// When
let newMonitor = NetworkMonitor()
// Then
XCTAssertNotNil(newMonitor)
let isConnected = newMonitor.isConnected
XCTAssertTrue(isConnected || !isConnected)
newMonitor.stopMonitoring()
}
// MARK: - Integration Tests
func testNetworkMonitor_WorksWithActualNetwork() async throws {
// This test verifies that the monitor works with the actual network
// It's an integration test that depends on the device's network state
// Wait a moment for the monitor to update
try await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds
// Just verify we can read the values without crashing
let isConnected = sut.isConnected
let connectionType = sut.connectionType
// Log the actual values for debugging
print("Network connected: \(isConnected)")
print("Connection type: \(connectionType.rawValue)")
// Verify we got valid values
XCTAssertTrue(isConnected || !isConnected)
let validTypes: [ConnectionType] = [.wifi, .cellular, .ethernet, .unknown]
XCTAssertTrue(validTypes.contains(connectionType))
}
// MARK: - Memory Tests
func testNoMemoryLeak_WhenCreatedAndDestroyed() {
// Given
weak var weakMonitor: NetworkMonitor?
autoreleasepool {
let monitor = NetworkMonitor()
weakMonitor = monitor
monitor.stopMonitoring()
}
// Note: Due to internal dispatch queues and NWPathMonitor,
// the monitor may not be immediately deallocated.
// This test primarily verifies no crash occurs.
XCTAssertTrue(true)
}
}
// MARK: - MockNetworkMonitor Tests
/// Tests for the MockNetworkMonitor used in other tests
final class MockNetworkMonitorTests: XCTestCase {
// MARK: - Properties
private var mockMonitor: MockNetworkMonitor!
// MARK: - Test Lifecycle
override func setUp() {
super.setUp()
mockMonitor = MockNetworkMonitor(isConnected: true)
}
override func tearDown() {
mockMonitor = nil
super.tearDown()
}
// MARK: - Mock Behavior Tests
func testMockNetworkMonitor_WhenInitializedConnected_ReportsConnected() {
// Given
let monitor = MockNetworkMonitor(isConnected: true)
// Then
XCTAssertTrue(monitor.isConnected)
}
func testMockNetworkMonitor_WhenInitializedDisconnected_ReportsDisconnected() {
// Given
let monitor = MockNetworkMonitor(isConnected: false)
// Then
XCTAssertFalse(monitor.isConnected)
}
func testMockNetworkMonitor_CanChangeConnectionStatus() {
// Given
let monitor = MockNetworkMonitor(isConnected: true)
XCTAssertTrue(monitor.isConnected)
// When
monitor.isConnected = false
// Then
XCTAssertFalse(monitor.isConnected)
}
func testMockNetworkMonitor_TrackStartMonitoringCalls() {
// When
mockMonitor.startMonitoring()
// Then
XCTAssertEqual(mockMonitor.startMonitoringCallCount, 1)
}
func testMockNetworkMonitor_TrackStopMonitoringCalls() {
// When
mockMonitor.stopMonitoring()
// Then
XCTAssertEqual(mockMonitor.stopMonitoringCallCount, 1)
}
}