SwiftUI应用单元测试失败:预期40条记录实际返回0条
单元测试失败排查:水果列表应用返回记录数为0而非预期40条
我使用SwiftUI开发了一款水果列表应用,并编写了单元测试用例,测试文件夹中存放了本地JSON文件。预期测试能够通过,但实际运行时发现返回记录数为0,而非预期的40条。错误信息如下:
testTotalCountWithSuccess(): XCTAssertEqual failed: ("0") is not equal to ("40") - Total record is matched
ContentView代码
import SwiftUI struct ContentView: View { @EnvironmentObject private var viewModel: FruitListViewModel @State private var filteredFruits: [Fruits] = [] @State var searchText = "" var body: some View { NavigationView { List { ForEach(fruits) { fruit in NavigationLink(destination: FruitDetailsView(fruit: fruit)) { RowView(name: fruit.name, genus: fruit.genus, family: fruit.family) } } } .searchable(text: $searchText) .onChange(of: searchText, perform: performSearch) .task { viewModel.fetchFruit() } .navigationTitle("Fruits List") } .onAppear { viewModel.fetchFruit() } } private func performSearch(keyword: String) { filteredFruits = viewModel.fruits.filter { fruit in fruit.name.contains(keyword) } } private var fruits: [Fruits] { filteredFruits.isEmpty ? viewModel.fruits: filteredFruits } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
ViewModel代码
protocol FruitListViewModelType { func fetchFruit() } class FruitListViewModel: FruitListViewModelType, ObservableObject { private let service:Service! @Published private(set) var fruits = [Fruits]() init(service:Service = ServiceImpl()) { self.service = service } func fetchFruit() { let client = ServiceClient(baseUrl:EndPoints.baseUrl.rawValue, path:Path.cakeList.rawValue, params:"", method:"get") service.fetchData(client:client, type:[Fruits].self) { [weak self] (result) in switch result { case .success(let result): DispatchQueue.main.async { self?.fruits = result } case .failure(let error): DispatchQueue.main.async { print(error.localizedDescription) self?.fruits = [] } } } } }
MockService代码
import Foundation @testable import FruitsDemoSwiftUI class MockService: Service, JsonDecodable { var responseFileName = "" func fetchData<T>(client: ServiceClient, type: T.Type, completionHandler: @escaping Completion<T>) where T : Decodable, T : Encodable { // Obtain Reference to Bundle let bundle = Bundle(for:MockService.self) guard let url = bundle.url(forResource:responseFileName, withExtension:"json"), let data = try? Data(contentsOf: url), let output = decode(input:data, type:T.self) else { completionHandler(.failure(ServiceError.parsinFailed(message:"Failed to get response"))) return } completionHandler(.success(output)) } }
单元测试代码
import XCTest @testable import FruitsDemoSwiftUI final class FruitsDemoSwiftUITests: XCTestCase { var mockService: MockService! var viewModel: FruitListViewModel! override func setUp() { mockService = MockService() viewModel = FruitListViewModel(service: mockService) } func testTotalCountWithSuccess() { mockService.responseFileName = "FruitSuccessResponse" viewModel.fetchFruit() let resultCount = viewModel.fruits.count XCTAssertEqual(resultCount ,40, "Total record is matched")// it should return 40 record but it return 0. } }
失败原因及解决方法
1. 异步操作未等待,测试提前断言
viewModel.fetchFruit()是异步方法,调用后立刻读取viewModel.fruits.count时,MockService的回调还未执行,此时fruits仍是初始空数组。
解决方法:使用XCTestExpectation等待异步操作完成:
func testTotalCountWithSuccess() { let expectation = XCTestExpectation(description: "Fruits loaded successfully") mockService.responseFileName = "FruitSuccessResponse" // 监听fruits的Published值变化 let cancellable = viewModel.$fruits.sink { fruits in if fruits.count == 40 { expectation.fulfill() } } viewModel.fetchFruit() wait(for: [expectation], timeout: 1.0) XCTAssertEqual(viewModel.fruits.count, 40, "Total record is matched") cancellable.cancel() }
2. JSON文件配置问题
检查FruitSuccessResponse.json文件:
- 确认已添加到测试Target的Bundle中(Xcode中选中文件,右侧"Target Membership"需勾选测试目标)
- 文件名拼写完全一致(iOS Bundle区分大小写)
- JSON格式与
Fruits模型结构匹配,确保能正常解码
3. 解码逻辑异常
检查JsonDecodable协议的decode方法实现,确认它能正确将JSON数据解码为[Fruits]类型。若JSON结构与模型不匹配,会触发解码失败,导致fruits被设为空数组。
4. 主线程异步更新的延迟
即使MockService同步读取本地文件,回调中使用DispatchQueue.main.async更新fruits,主线程队列任务需等待当前测试代码执行完毕才会运行,因此测试代码会先读取到空数组。通过XCTestExpectation监听fruits的变化可解决此问题。
内容的提问来源于stack exchange,提问作者user8705895
相关产品推荐
相关产品推荐

