Awaiting fulfillment(of:timeout:enforceOrder:) doesn’t wait?

I'm trying to unit test some code that has a series of async operations (StoreKit code). I have to sink on a @Publisher and wait to get the right value, then call a method, and do it again. But it seems awaiting fulfillment doesn’t actually block execution:

In the following code, there are two await fulfillment(of:) calls. I had expected the first one to wait until it was fulfilled, or until it timed out. But it does not, and the code immediately proceeds to delete the transaction it had created a bit earlier.

This is very counterintuitive to me. I can't imagine it's considered to be a bug, so is there another way for me to essentially stop the progress of a unit test until an @Published property has a particular value?

let session = try SKTestSession(configurationFileNamed: "Re-Key")
session.clearTransactions()

let purchaseExpectation = XCTestExpectation(description: "purchaseExpectation")

let store = Store.shared
_ = store.$expires.sink
{ inValue in
	if let hasSub = store.hasSubscription,
		hasSub
	{
		purchaseExpectation.fulfill()
	}
}

let txn = try await session.buyProduct(identifier: "rekey.lifetime")
await fulfillment(of: [purchaseExpectation], timeout: 5.0)

//	Delete the purchase…

let deleteExpectation = XCTestExpectation(description: "deleteExpectation")
_ = store.$expires.sink
{ inValue in
	if store.hasSubscription == nil
	{
		deleteExpectation.fulfill()
	}
}

try session.deleteTransaction(identifier: UInt(txn.id))
await fulfillment(of: [deleteExpectation], timeout: 5.0)

You're saying purchaseExpectation isn't fulfilled by the time await fulfillment completes? If so, that definitely a bug, given its purpose. But fulfillment of that expectation is all that statement waits for.

Sure seems like what I'm seeing. Let me see if I can't make a self-contained example.

I'm not sure if this is the same issue but I recall being surprised in unit tests when sink would be called for my initial value, not what I was changing in my unit test. You may want to check swift - Prevent sink receiveValue closure from being called immediately - Stack Overflow

2 Likes

I did have this issue but I had addressed it. But perhaps it confounded my current problem because it does seem to be behaving properly now.

1 Like