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

Swift异步函数单元测试报错:Task创建于已失效会话

问题分析与解决方案

核心错误原因

报错Thread 1: "Task created in a session that has been invalidated"的根源在于代码存在两个关键缺陷:

  1. computePrice函数存在未调用completion的分支:JSON解码失败的catch块中,仅更新了showingInvalidAlert但未触发completion,导致测试中的expectation永远无法完成。测试超时后,XCTest框架清理资源时会间接引发会话无效的错误。
  2. 断言可能在非主线程执行:网络错误分支里的completion(nil)直接在后台线程调用,而XCTest的断言必须在主线程执行,这会导致测试不稳定甚至触发异常。

分步解决方案

1. 修复ViewModel的completion调用路径

确保所有分支都调用completion,避免expectation永久等待:

func computePrice(_ fromLat: Double, _ fromLon: Double,_ toLat: Double, _ toLon: Double, completion: @escaping (Double?) -> Void){
    let url = URL(string:"NotShowingMyUrlToStackOverflowButPretendItsHere")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    print("fromLat: \(fromLat)    fromLon: \(fromLon)   toLat: \(toLat)    toLon: \(toLon)")
    let body = [
        "from": [
            "latitude": fromLat,
            "longitude": fromLon
        ],
        "to": [
            "latitude": toLat,
            "longitude": toLon
        ]
    ]
    let jsonData = try? JSONSerialization.data(withJSONObject: body)
    request.httpBody = jsonData
    
    URLSession.shared.dataTask(with: request){ data, response, error in
        guard let data = data, error == nil,
              (response as? HTTPURLResponse)?.statusCode == 200
        else{
            // 统一切换到主线程调用completion
            DispatchQueue.main.async {
                completion(nil)
            }
            return
        }
        
        //Decode JSON response
        do {
            let decoder = JSONDecoder()
            let response1 = try decoder.decode(PriceResponse.self, from: data)
            DispatchQueue.main.async {
                completion(response1.price)
                self.screenDisabled = false
            }
        } catch {
            DispatchQueue.main.async {
                self.showingInvalidAlert = true
                // 新增:解码失败时调用completion
                completion(nil)
            }
            print("THE ERROR IS \(error.localizedDescription)")
        }
        
    }.resume()
}

2. 确保测试断言在主线程执行

修改测试代码,将断言放在主线程中执行,避免非主线程操作XCTest API:

func testRswPrice(){
    //Given (Arrange)
    let fromLat = 26.336502
    let fromLon = -81.439354
    let toLat = 26.533842
    let toLon = -81.759150
    let vm = LocationSearchViewModel()
    
    //When (Act)
    let expectation = self.expectation(description: "Compute Price")
    vm.computePrice(fromLat, fromLon, toLat, toLon){ value in
        DispatchQueue.main.async {
            expectation.fulfill()
            XCTAssertEqual(value, 8000.0)
        }
    }
    // Wait for the expectation to be fulfilled, or time out after a specified duration
    wait(for: [expectation], timeout: 20.0)
}

3. 检查测试目标的网络配置(可选)

若API使用HTTP协议,需确保测试目标的Info.plist配置了NSAppTransportSecurity以允许非HTTPS请求:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

注:生产环境建议使用HTTPS,此配置仅用于测试或临时场景。

4. 注入URLSession提升测试可控性(进阶)

为避免依赖真实网络,可在ViewModel中注入URLSession,测试时使用Mock Session:

// 修改ViewModel初始化方法
class LocationSearchViewModel {
    private let urlSession: URLSession
    
    init(urlSession: URLSession = .shared) {
        self.urlSession = urlSession
    }
    
    func computePrice(_ fromLat: Double, _ fromLon: Double,_ toLat: Double, _ toLon: Double, completion: @escaping (Double?) -> Void){
        // 替换URLSession.shared为self.urlSession
        self.urlSession.dataTask(with: request){ data, response, error in
            // 回调逻辑不变
        }.resume()
    }
}

// 测试时使用自定义Mock URLSession(需自行实现Mock逻辑)
func testRswPrice(){
    let mockSession = URLSessionMock()
    let vm = LocationSearchViewModel(urlSession: mockSession)
    // 模拟响应数据并执行测试
}

验证方法

修复后重新运行测试:

  • 若API返回正常,断言会验证价格是否符合预期
  • 若出现网络或解码错误,completion会被正常调用,expectation不会永久等待,避免会话无效的错误

内容的提问来源于stack exchange,提问作者John Citrowske

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 01:15:56