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 ..
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()
}
}
Here is the view model ..
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 = []
}
}
}
}
}
Here is the my Mock api class .
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))
}
}
Here is the unit test code ..
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 shroud return 40 record but it return 0.
}
}
Here is the link for Json ..
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.
What you need to do here is to use XCTestExpectation as follows:
let expectation = expectation(description: "Wait for async code to complete")
service.load {
// after load completes
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
In your case you'll need to update your viewModel's fetchFruit
function to have a completion closure as a parameter that you can pass from within your test. Something like:
func fetchFruit(completion: ((Bool) -> ())? = nil) {
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
completion?(true)
}
case .failure(let error):
DispatchQueue.main.async {
print(error.localizedDescription)
self?.fruits = []
completion?(false)
}
}
}
}
And then calling it like:
viewModel.fetchFruit { result in ... }