iosswiftswiftuicombine

Unit testing a published variable


I have a set up like this:

class ViewModel: ObservableObject {
    @Published var data: [Int] = []

    func fetchData() {
        someAPICall() { result in
            data = result // Assume 10 items get inserted here
        }
    }
}

I write a test like so:

func testViewModel() {
    let waitExpectation = expectation(description: "testVM")
    let viewModel = ViewModel()
    viewModel.$contentUnitViewModels
    .dropFirst() // Ignore the publish that happens when the VM is initialized
    .sink { data in
        waitExpectation.fulfill()
        XCTAssertEqual(viewModel.data.count, 10)
        // viewModel.data is empty but data has the expected 10 values
    }
    .store(in: &cancellables)

    viewModel.fetchData()

    waitForExpectations(timeout: waitExpectationTimeout) { error in
        XCTAssertNil(error)
    }

}

The issue is that when I used the published property data with a SwiftUI view, the data gets rendered / loaded as expected.

However, during tests, for some reason, viewModel.data is empty and viewModel.data.count is 0.

Funnily enough, the data property passed within the completion handler has 10 values.

It seems that the publish happens before the actual underlying array is set.

Is there anyway to fix this / improve my code so that I only get notified when the actual underlying array is set ?


Solution

  • You could improve it by using task then you don't need the object, eg

    .task {
        results = await controller.fetch()
    }
    

    The advantage with task is it runs on appear and cancels on dissapear, which you forgot to implement in your object. Also, now the async func in the controller can be tested.