Swift UI 应用在单元测试用例上出现问题。

huangapple go评论108阅读模式
英文:

Swift UI Application falling on Unit test cases

问题

I create the app into Swift UI. I wrote the Unit testing case. I have local json into the test folder. I am expecting to pass, but it's showing that it found the record 0 instead of returning the total record. Here is the error message:

testTotalCountWithSuccess(): XCTAssertEqual failed: ("0") is not equal to ("40") - Total record is matched

Here is code for Content view:

  1. import SwiftUI
  2. struct ContentView: View {
  3. @EnvironmentObject private var viewModel: FruitListViewModel
  4. @State private var filteredFruits: [Fruits] = []
  5. @State var searchText = ""
  6. var body: some View {
  7. NavigationView {
  8. List {
  9. ForEach(fruits) { fruit in
  10. NavigationLink(destination: FruitDetailsView(fruit: fruit)) {
  11. RowView(name: fruit.name, genus: fruit.genus, family: fruit.family)
  12. }
  13. }
  14. }
  15. .searchable(text: $searchText)
  16. .onChange(of: searchText, perform: performSearch)
  17. .task {
  18. viewModel.fetchFruit()
  19. }
  20. .navigationTitle("Fruits List")
  21. }
  22. .onAppear {
  23. viewModel.fetchFruit()
  24. }
  25. }
  26. private func performSearch(keyword: String) {
  27. filteredFruits = viewModel.fruits.filter { fruit in
  28. fruit.name.contains(keyword)
  29. }
  30. }
  31. private var fruits: [Fruits] {
  32. filteredFruits.isEmpty ? viewModel.fruits : filteredFruits
  33. }
  34. }
  35. struct ContentView_Previews: PreviewProvider {
  36. static var previews: some View {
  37. ContentView()
  38. }
  39. }

Here is the view model:

  1. protocol FruitListViewModelType {
  2. func fetchFruit()
  3. }
  4. class FruitListViewModel: FruitListViewModelType, ObservableObject {
  5. private let service: Service!
  6. @Published private(set) var fruits = [Fruits]()
  7. init(service: Service = ServiceImpl()) {
  8. self.service = service
  9. }
  10. func fetchFruit() {
  11. let client = ServiceClient(baseUrl: EndPoints.baseUrl.rawValue, path: Path.cakeList.rawValue, params: "", method: "get")
  12. service.fetchData(client: client, type: [Fruits].self) { [weak self] (result) in
  13. switch result {
  14. case .success(let result):
  15. DispatchQueue.main.async {
  16. self?.fruits = result
  17. }
  18. case .failure(let error):
  19. DispatchQueue.main.async {
  20. print(error.localizedDescription)
  21. self?.fruits = []
  22. }
  23. }
  24. }
  25. }
  26. }

Here is the Mock API class:

  1. import Foundation
  2. @testable import FruitsDemoSwiftUI
  3. class MockService: Service, JsonDecodable {
  4. var responseFileName = ""
  5. func fetchData<T>(client: ServiceClient, type: T.Type, completionHandler: @escaping Completion<T>) where T: Decodable, T: Encodable {
  6. // Obtain Reference to Bundle
  7. let bundle = Bundle(for: MockService.self)
  8. guard let url = bundle.url(forResource: responseFileName, withExtension: "json"),
  9. let data = try? Data(contentsOf: url),
  10. let output = decode(input: data, type: T.self)
  11. else {
  12. completionHandler(.failure(ServiceError.parsinFailed(message: "Failed to get response")))
  13. return
  14. }
  15. completionHandler(.success(output))
  16. }
  17. }

Here is the unit test code:

  1. import XCTest
  2. @testable import FruitsDemoSwiftUI
  3. final class FruitsDemoSwiftUITests: XCTestCase {
  4. var mockService: MockService!
  5. var viewModel: FruitListViewModel!
  6. override func setUp() {
  7. mockService = MockService()
  8. viewModel = FruitListViewModel(service: mockService)
  9. }
  10. func testTotalCountWithSuccess() {
  11. mockService.responseFileName = "FruitSuccessResponse"
  12. viewModel.fetchFruit()
  13. let resultCount = viewModel.fruits.count
  14. XCTAssertEqual(resultCount, 40, "Total record is matched") // It should return 40 records, but it returns 0.
  15. }
  16. }

Here is the link for JSON:

https://fruityvice.com/api/fruit/all

Here is the screenshot:
Swift UI 应用在单元测试用例上出现问题。

英文:

I create the app into Swift UI . I wrote the Unit testing case . I have local json into test folder . I am expecting to pass but it showing it found the record 0 instead of returning total record . Here is the error message ..

testTotalCountWithSuccess(): XCTAssertEqual failed: ("0") is not equal to ("40") - Total record is matched
Here is code for Content view ..

  1. import SwiftUI
  2. struct ContentView: View {
  3. @EnvironmentObject private var viewModel: FruitListViewModel
  4. @State private var filteredFruits: [Fruits] = []
  5. @State var searchText = &quot;&quot;
  6. var body: some View {
  7. NavigationView {
  8. List {
  9. ForEach(fruits) { fruit in
  10. NavigationLink(destination: FruitDetailsView(fruit: fruit)) {
  11. RowView(name: fruit.name, genus: fruit.genus, family: fruit.family)
  12. }
  13. }
  14. }
  15. .searchable(text: $searchText)
  16. .onChange(of: searchText, perform: performSearch)
  17. .task {
  18. viewModel.fetchFruit()
  19. }
  20. .navigationTitle(&quot;Fruits List&quot;)
  21. }
  22. .onAppear {
  23. viewModel.fetchFruit()
  24. }
  25. }
  26. private func performSearch(keyword: String) {
  27. filteredFruits = viewModel.fruits.filter { fruit in
  28. fruit.name.contains(keyword)
  29. }
  30. }
  31. private var fruits: [Fruits] {
  32. filteredFruits.isEmpty ? viewModel.fruits: filteredFruits
  33. }
  34. }
  35. struct ContentView_Previews: PreviewProvider {
  36. static var previews: some View {
  37. ContentView()
  38. }
  39. }

Here is the view model ..

  1. protocol FruitListViewModelType {
  2. func fetchFruit()
  3. }
  4. class FruitListViewModel: FruitListViewModelType, ObservableObject {
  5. private let service:Service!
  6. @Published private(set) var fruits = [Fruits]()
  7. init(service:Service = ServiceImpl()) {
  8. self.service = service
  9. }
  10. func fetchFruit() {
  11. let client = ServiceClient(baseUrl:EndPoints.baseUrl.rawValue, path:Path.cakeList.rawValue, params:&quot;&quot;, method:&quot;get&quot;)
  12. service.fetchData(client:client, type:[Fruits].self) { [weak self] (result) in
  13. switch result {
  14. case .success(let result):
  15. DispatchQueue.main.async {
  16. self?.fruits = result
  17. }
  18. case .failure(let error):
  19. DispatchQueue.main.async {
  20. print(error.localizedDescription)
  21. self?.fruits = []
  22. }
  23. }
  24. }
  25. }
  26. }

Here is the my Mock api class .

  1. import Foundation
  2. @testable import FruitsDemoSwiftUI
  3. class MockService: Service, JsonDecodable {
  4. var responseFileName = &quot;&quot;
  5. func fetchData&lt;T&gt;(client: ServiceClient, type: T.Type, completionHandler: @escaping Completion&lt;T&gt;) where T : Decodable, T : Encodable {
  6. // Obtain Reference to Bundle
  7. let bundle = Bundle(for:MockService.self)
  8. guard let url = bundle.url(forResource:responseFileName, withExtension:&quot;json&quot;),
  9. let data = try? Data(contentsOf: url),
  10. let output = decode(input:data, type:T.self)
  11. else {
  12. completionHandler(.failure(ServiceError.parsinFailed(message:&quot;Failed to get response&quot;)))
  13. return
  14. }
  15. completionHandler(.success(output))
  16. }
  17. }

Here is the unit test code ..

  1. import XCTest
  2. @testable import FruitsDemoSwiftUI
  3. final class FruitsDemoSwiftUITests: XCTestCase {
  4. var mockService: MockService!
  5. var viewModel: FruitListViewModel!
  6. override func setUp() {
  7. mockService = MockService()
  8. viewModel = FruitListViewModel(service: mockService)
  9. }
  10. func testTotalCountWithSuccess() {
  11. mockService.responseFileName = &quot;FruitSuccessResponse&quot;
  12. viewModel.fetchFruit()
  13. let resultCount = viewModel.fruits.count
  14. XCTAssertEqual(resultCount ,40, &quot;Total record is matched&quot;)// it shroud return 40 record but it return 0.
  15. }
  16. }

Here is the link for Json ..

https://fruityvice.com/api/fruit/all

Here is the screenshot ..
Swift UI 应用在单元测试用例上出现问题。

答案1

得分: 0

  1. `testTotalCountWithSuccess`测试的是异步代码。当你在第23行调用`fetchFruit`时,会发生异步操作。即使你使用了MockService,完成处理程序将在*稍后的某个时间点*被调用。这个时间点在你在第2425行检查`fruits`计数之后。所以当你执行XCTAssert时,你没有值可以进行比较。
  2. 在这里需要做的是使用[XCTestExpectation][1],如下所示:
  3. ```swift
  4. let expectation = expectation(description: "Wait for async code to complete")
  5. service.load {
  6. // 在加载完成后
  7. expectation.fulfill()
  8. }
  9. wait(for: [expectation], timeout: 2.0)

在你的情况下,你需要更新viewModel的fetchFruit函数,使其具有一个作为参数的完成闭包,你可以在测试中传递。类似于:

  1. func fetchFruit(completion: ((Bool) -> ())? = nil) {
  2. //...(函数体不变)
  3. }
  4. viewModel.fetchFruit { result in ... }
  1. <details>
  2. <summary>英文:</summary>
  3. The `testTotalCountWithSuccess` test is testing asynchronous code. When you call `fetchFruit` on line 23, an async operation takes place. Even though you are using your MockService, the completion handler will be called *at some later point in time*. This point in time is after you are checking against your `fruits` count on line 24 and 25. So by the time you are executing XCTAssert you have no value to compare against.
  4. What you need to do here is to use [XCTestExpectation][1] as follows:
  5. let expectation = expectation(description: &quot;Wait for async code to complete&quot;)
  6. service.load {
  7. // after load completes
  8. expectation.fulfill()
  9. }
  10. wait(for: [expectation], timeout: 2.0)
  11. In your case you&#39;ll need to update your viewModel&#39;s `fetchFruit` function to have a completion closure as a parameter that you can pass from within your test. Something like:
  12. func fetchFruit(completion: ((Bool) -&gt; ())? = nil) {
  13. let client = ServiceClient(baseUrl:EndPoints.baseUrl.rawValue, path:Path.cakeList.rawValue, params:&quot;&quot;, method:&quot;get&quot;)
  14. service.fetchData(client:client, type:[Fruits].self) { [weak self] (result) in
  15. switch result {
  16. case .success(let result):
  17. DispatchQueue.main.async {
  18. self?.fruits = result
  19. completion?(true)
  20. }
  21. case .failure(let error):
  22. DispatchQueue.main.async {
  23. print(error.localizedDescription)
  24. self?.fruits = []
  25. completion?(false)
  26. }
  27. }
  28. }
  29. }
  30. And then calling it like:
  31. viewModel.fetchFruit { result in ... }
  32. [1]: https://developer.apple.com/documentation/xctest/xctestexpectation
  33. </details>

huangapple
  • 本文由 发表于 2023年3月7日 22:32:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/75663308.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定