I am trying to test an observable object. This will make a network request which I have mocked out with dependency injection. The problem I am facing is even though i am awaiting my async function to finish, my assertion in my unit test triggers before setting my published properties. I am for sure missing something but just want any advice to clean up my view model or my tests.
The flow of my vm is as follows. call my fetchWhatsNew function. parse out the data and verify we have a true for show marketing, and a valid url. if so, check a published boolean(shouldShowMarketingPage) true. Then in my swiftUI views I will call the fetch function and display the url if needed.
// View model
final class MarketingViewModel: ObservableObject {
/// Decides if we need to show the marketing page to the user
@Published private(set) var shouldShowMarketingPage: Bool = false
@Published private(set) var whatNewUrl: String?
/// bool value from the api response
private var showMarketingFromApi = false
private(set) var error: Error?
private let networkingManager: NetworkingManagerImplementation
private let marketingRowId: String = "i-3JdpsB-rxH"
init(netwokringManager: NetworkingManagerImplementation = NetworkingManger.shared) {
self.networkingManager = netwokringManager
}
func fetchWhatsNew() async {
do {
let res = try await networkingManager.request(session: .shared, .getCodaApiData(documentId: .AppDoc,
tableId: .marketingTableId),
type: ShowMarketingInApp.self)
handleResponse(response: res)
} catch {
print("whats new error: ", error.localizedDescription)
self.error = error
}
}
private func handleResponse(response: ShowMarketingInApp) {
if let marketingRow = response.items.first(where: { $0.id == marketingRowId }) {
DispatchQueue.main.async {
self.showMarketingFromApi = marketingRow.values.showMarketing
self.whatNewUrl = marketingRow.values.marketingUrl
self.checkShouldShowMarketingPage()
}
}
}
private func checkShouldShowMarketingPage() {
guard showMarketingFromApi, isValidURL(whatNewUrl) else {
self.shouldShowMarketingPage = false
return
}
self.shouldShowMarketingPage = true
}
private func isValidURL(_ urlString: String?) -> Bool {
guard let urlString = urlString, let _ = URL(string: urlString) else {
return false
}
return true
}
}
// test class
class MarketingVmSuccessTests: XCTestCase {
private var networkMock: NetworkingManagerImplementation!
private var marketingVM: MarketingViewModel!
override func setUp() async throws {
try await super.setUp()
}
override func tearDown() async throws {
networkMock = nil
marketingVM = nil
try await super.tearDown()
}
private func createViewModel(with successType: MarketingMockSuccessType) {
networkMock = MarketingNetworkManagerSuccessMock(successType: successType)
marketingVM = MarketingViewModel(netwokringManager: networkMock)
}
func test_FetchWhatsNew_Success_showMarketing_hasUrl() async {
createViewModel(with: .showMarketing_hasUrl)
await marketingVM.fetchWhatsNew()
XCTAssertTrue(marketingVM.shouldShowMarketingPage, "show marketing page should be true")
XCTAssertEqual(marketingVM.whatNewUrl, "https://myurl.com", "Urls dont match")
}
I put a break point on my first assertion and I put a break point on my final function in VM(isValidURL) and my assertion fires first.