You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 19:49:53