How to know XCTestExpectation current fulfillment count
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
XCTestExpectation lets you describe how many asynchronous events must happen before a test can finish, but it is not designed as a general-purpose counter you can query at any time. In practice, if you need the current fulfillment count during a test, the reliable approach is to track that count yourself next to the expectation.
What XCTest Gives You Directly
XCTestExpectation exposes a few useful pieces of state, including the description and the expectedFulfillmentCount. That tells XCTest how many times fulfill() must be called before the expectation is satisfied.
Example:
This sets the target count, but it still does not give you a convenient public API for "how many fulfillments have happened so far." That is the gap most people run into.
Track The Current Count Yourself
If the test logic needs to inspect the current number of callbacks, keep your own counter alongside the expectation.
This works because your test is now explicit about the state it cares about. XCTest handles waiting. Your own counter handles inspection and assertions.
For older synchronous-style tests, the equivalent pattern is:
Why XCTest Does Not Center This API
An expectation is mainly a synchronization primitive. XCTest cares whether the expectation was fulfilled enough times, fulfilled too many times, or timed out. It does not encourage business logic that polls internal expectation state while the async work is still happening.
That design is sensible because tests are easier to maintain when they assert observable results:
- data was returned
- callback fired the expected number of times
- timeout did not occur
If you find yourself constantly needing the expectation's internal count, it is often a sign that your test should expose that state through a stub, spy, or helper object instead.
A Better Pattern For Repeated Callbacks
For more complex tests, use a small recorder object. That keeps the count and payloads together.
This gives you a current count through recorder.values.count and a full record of what happened.
Use assertForOverFulfill
If you expect a fixed number of callbacks, enable:
That helps catch accidental extra fulfill() calls. It is not a counter, but it makes the test stricter and easier to debug.
Common Pitfalls
- Expecting
expectedFulfillmentCountto tell you how many timesfulfill()has already been called. It only tells XCTest the target number. - Polling expectation state instead of tracking callback state in your test double or helper object.
- Forgetting
assertForOverFulfillwhen duplicate callbacks would indicate a bug. - Mixing old
wait(for:timeout:)style and new async test style inconsistently in the same test logic. - Using expectations when a direct async return value would make the test simpler.
Summary
- '
XCTestExpectationis built for synchronization, not for exposing a live fulfillment counter.' - Use
expectedFulfillmentCountto define the target number of callbacks. - If you need the current count, track it yourself in a variable, spy, or recorder object.
- '
assertForOverFulfillhelps catch extra callback invocations.' - Prefer assertions on observable test state rather than trying to inspect expectation internals.

