Unit Testing asynchronous functions called by various didSets in Swift

Viewed 130

I'm trying to unit test a property in a ViewModel (in Swift) that is dependent on a series of other properties being set, and having trouble doing so.

I have a viewModel which includes a timeLength, a timePeriod, and a listOfObjects that fits my time period.

These values depend on the previous one,

timeLength and timePeriod are both stored in the user's profile, and my code follows this flow:

  1. Search for a timeLength in the profile.
  2. If none is found, allow the user to select a timeLength
  3. Once the timeLength is set, a didSet triggers a flow that searches for a matching timePeriod, (in a mock repository for time periods) or creates one if one doesn't exist.
  4. Once the timePeriod exists, a didSet triggers a flow that searches a database to compile a listOfObjects that match that timePeriod (using a separate mockRepository for objects).

I'm trying to create a unit test that checks the list of objects once that code flow is completed, but every time I do so, the XCTAssertEqual method completes before the code flow is finished. I tried using XCTestExpectation, as described in Hacking with Swift, but I don't have a specific asynchronous method to call, since this is all triggered by a series of didSet calls.

I could create a timePeriod in the repository, which would trigger the listOfObjects to be set in the ViewModel, but then I'd be missing out on the full flow of this. Is there a way to just have the test complete after a few seconds? (Or is this a bad test since I'm relying on the system to do multiple things at once in order to pass the test?)

Here's the specific test (right now it won't do anything since there's nothing to fulfill the asynchronous wait)

    func testSeasonObjectsVM_loadData() {
        // given
        let exp = expectation(description: "loading Data")
        sut.timeLength = .quarter
        waitForExpectations(timeout: 20)
        
        // when
        let count = sut.listOfObjects.count
        
        // assert
        XCTAssertEqual(count, 2)
        
    }
1 Answers

You can fire a notification from the final place that you know the process / series of didSets has been completed like following.

public extension Notification.Name {
    static let UserProfileViewModelListOfObjectsLoaded =
        Notification.Name(rawValue: "UserProfileViewModelListOfObjectsLoaded")
}

// Your viewModel's final didSet
var listOfObjects: [Any] {
    didSet {
        NotificationCenter.default.post(name: .UserProfileViewModelListOfObjectsLoaded, object: nil)
    }
}

// In your test
self.expectation(forNotification: .UserProfileViewModelListOfObjectsLoaded, object: nil) { (notification) -> Bool in
    return (sut.listOfObjects.count == 2)
}
Related