Cuckoo icon indicating copy to clipboard operation
Cuckoo copied to clipboard

Introduce timeout and after to support delayed verification.

Open sk409 opened this issue 3 years ago • 1 comments

I introduced these two global functions to support delayed verification discussed in this issue(#277).

// timeout waits until CallMatcher::matches returns true and exits on success.
public func timeout(_ timeoutDuration: TimeInterval, waitingDuration: TimeInterval = 0.01) -> ContinuationWithTimeout
// after waits until delayDuration elapsed and exits when CallMatcher::matches never return true.
public func after(_ delayDuration: TimeInterval, waitingDuration: TimeInterval = 0.01) -> ContinueationAfterDelay

ContinuationWithTimeout and ContinueationAfterDelay conform to Continuation protocol.

Continuation determines whether CallMatcher::matches should be called again, waits, etc.

like this:

// MockManager
public func verify<IN, OUT>(_ method: String, callMatcher: CallMatcher, parameterMatchers: [ParameterMatcher<IN>], continuation: Continuation, sourceLocation: SourceLocation) -> __DoNotUse<IN, OUT> {
        ...
        while continuation.check() {
            calls = []
            indexesToRemove = []
            for (i, stubCall) in stubCalls.enumerated() {
                if let stubCall = stubCall as? ConcreteStubCall<IN> , (parameterMatchers.reduce(stubCall.method == method) { $0 && $1.matches(stubCall.parameters) }) {
                    calls.append(stubCall)
                    indexesToRemove.append(i)
                }
            }
            matches = callMatcher.matches(calls)
            if !matches && !callMatcher.canRecoverFromFailure(calls) {
                break
            }
            if matches && continuation.exitOnSuccess {
                break
            }
            continuation.wait()
        }
        ...
        return __DoNotUse()
    }

Users can simply write test of fire-and-forget asynchronous function and asynchronous function with completion closure that does not require argument verification.

stub(mockTestClass) { stub in
    when(stub.f()).thenDoNothing()
}
// someMethod is expected to call f three times within three seconds.
mockTestClass.someMethod()
verify(mockTestClass, timeout(3).times(3)).f()

But I think that if users want to verify completion closure argument, They should use expectation.

stub(mockTestClass) { stub in
    when(stub.f()).thenDoNothing()
}
let expectation = XCTestExpectation(description: #function)
mockTestClass.someMethod() { i in
    XCTAssertEqual(i, 1)
    expectation.fulfill()
}
wait(for: [expectation], timeout: 3)
verify(mockTestClass, times(3)).f()

If users write below code, XCTAssertEqual may no be called.

stub(mockTestClass) { stub in
    when(stub.f()).thenDoNothing()
}
mockTestClass.someMethod() { i in
    XCTAssertEqual(i, 1)
}
verify(mockTestClass, timeout(3).times(3)).f()

This PR is a prototype. If this PR is acceptable, I will write comments, tests and explanation to README.md.

If there are any improvements, please point them out.

sk409 avatar Sep 03 '22 09:09 sk409

I like the API this provides, but I need some more context on the implementation before we go ahead with it.

MatyasKriz avatar Feb 07 '23 19:02 MatyasKriz