Swift网络请求单元测试:MockDataService的fetchBusinesses方法实现
实现MockDataService以覆盖测试场景
要模拟DataService的各种异常和正常场景,我们可以创建一个遵循DataServiceProtocol的MockDataService,通过预设场景来控制方法的行为,直接抛出对应错误或返回模拟数据,无需真实网络请求。
1. 定义场景枚举
先定义枚举来标识要模拟的测试场景:
enum MockDataServiceScenario { case success([Business]) // 正常返回商家数据 case invalidURL // 模拟URL无效场景 case decodingFailure // 模拟JSON解码失败场景 case invalidStatusCode(Int) // 模拟HTTP状态码异常场景 }
2. 实现MockDataService
final class MockDataService: DataServiceProtocol { // 控制当前模拟的场景 var scenario: MockDataServiceScenario = .success([]) func fetchBusinesses(location: CLLocationCoordinate2D) async throws -> [Business] { switch scenario { case .success(let businesses): // 返回预设的模拟商家数据 return businesses case .invalidURL: // 直接抛出URL无效错误,对应原服务中createURL失败的情况 throw DataServiceError.invalidURL case .decodingFailure: // 构造无效JSON数据,强制触发解码错误,和真实场景一致 let invalidJsonData = "not valid json".data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase // 这里会抛出DecodingError,无需额外处理 _ = try decoder.decode(SearchResponse.self, from: invalidJsonData) return [] case .invalidStatusCode(let code): // 抛出状态码异常错误,对应原服务中非200响应的情况 throw DataServiceError.invalidStatusCode(statusCode: code) } } }
3. 编写单元测试用例
以下是覆盖各场景的测试示例,确保DataServiceError实现Equatable以便断言:
import XCTest @testable import YourAppModule final class DataServiceTests: XCTestCase { private var mockService: MockDataService! override func setUp() { super.setUp() mockService = MockDataService() } override func tearDown() { mockService = nil super.tearDown() } // 测试URL无效场景 func testFetchBusinesses_InvalidURL_ThrowsError() async { mockService.scenario = .invalidURL let testLocation = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) do { _ = try await mockService.fetchBusinesses(location: testLocation) XCTFail("Expected invalidURL error") } catch let error as DataServiceError { XCTAssertEqual(error, .invalidURL) } catch { XCTFail("Unexpected error: \(error)") } } // 测试解码失败场景 func testFetchBusinesses_DecodingFailure_ThrowsDecodingError() async { mockService.scenario = .decodingFailure let testLocation = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) do { _ = try await mockService.fetchBusinesses(location: testLocation) XCTFail("Expected DecodingError") } catch is DecodingError { XCTAssertTrue(true, "Threw expected DecodingError") } catch { XCTFail("Unexpected error: \(error)") } } // 测试正常返回场景 func testFetchBusinesses_Success_ReturnsBusinesses() async throws { let mockBusinesses = [Business(id: "boba_1", name: "Test Boba Bar")] mockService.scenario = .success(mockBusinesses) let testLocation = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) let result = try await mockService.fetchBusinesses(location: testLocation) XCTAssertEqual(result, mockBusinesses) } // 测试状态码异常场景 func testFetchBusinesses_InvalidStatusCode_ThrowsError() async { let testCode = 404 mockService.scenario = .invalidStatusCode(testCode) let testLocation = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) do { _ = try await mockService.fetchBusinesses(location: testLocation) XCTFail("Expected invalidStatusCode error") } catch let error as DataServiceError { guard case .invalidStatusCode(let code) = error else { XCTFail("Wrong error type") return } XCTAssertEqual(code, testCode) } catch { XCTFail("Unexpected error: \(error)") } } }
注意事项
- 确保
DataServiceError、Business、SearchResponse这些类型在测试目标中可访问(比如标记为public或通过@testable import导入主模块)。 - 解码失败场景中,我们刻意使用无效JSON触发
DecodingError,和真实服务中遇到的错误类型完全一致,保证测试的真实性。
内容的提问来源于stack exchange,提问作者aDabOfRanch
相关产品推荐
相关产品推荐

