Testing a Timer in Xcode with Xctest

Testing a Timer in Xcode with XCTest

I ended up storing the original Timer's fireDate, then checking to see that after the action was performed the new fireDate was set to something later than the original fireDate.

func testTimerResets() {
let myObject = MyClass()
myObject.resetTimer()
let oldFireDate = myObject.timer!.fireDate
myObject.performAction()

// If timer did not reset, these will be equal
XCTAssertGreaterThan(myObject.timer!.fireDate, oldFireDate)
}

Unit Testing a Timer?

The sleep function blocks your thread and timers are associated to threads.
You should use expectation.

func testRefresh() {

var dataStore: DataStore = DataStore()
let expec = expectation(description: "Timer expectation") // create an expectation
dataStore.clinicsSignal.subscribeOnce(with: self) {
print("clinics signal = \($0)")
dataStore.clinicsSignal.fire(0)
expec.fulfill() // tell the expectation that everything's done
}

dataStore.load()
wait(for: [expec], timeout: 7.0) // wait for fulfilling every expectation (in this case only one), timeout must be greater than the timer interval
}

How Can I Unit Test Swift Timer Controller?

Rather than use a real timer (which would be slow), we can verify calls to a test double.

The challenge is that the code calls a factory method, Timer.scheduledTimer(…). This locks down a dependency. Testing would be easier if the test could provide a mock timer instead.

Usually, a good way to inject a factory is by supplying a closure. We can do this in the initializer, and provide a default value. Then the closure, by default, will make the actual call to the factory method.

In this case, it's a little complicated because the call to Timer.scheduledTimer(…) itself takes a closure:

internal init(seconds: Double,
makeRepeatingTimer: @escaping (TimeInterval, @escaping (TimerProtocol) -> Void) -> TimerProtocol = {
return Timer.scheduledTimer(withTimeInterval: $0, repeats: true, block: $1)
}) {
durationInSeconds = TimeInterval(seconds)
self.makeRepeatingTimer = makeRepeatingTimer
}

Note that I removed all references to Timer except inside the block. Everywhere else uses a newly-defined TimerProtocol.

self.makeRepeatingTimer is a closure property. Call it from startTimer(…).

Now test code can supply a different closure:

class TimerControllerTests: XCTestCase {
var makeRepeatingTimerCallCount = 0
var lastMockTimer: MockTimer?

func testSomething() {
let sut = TimerController(seconds: 12, makeRepeatingTimer: { [unowned self] interval, closure in
self.makeRepeatingTimerCallCount += 1
self.lastMockTimer = MockTimer(interval: interval, closure: closure)
return self.lastMockTimer!
})

// call something on sut

// verify against makeRepeatingTimerCallCount and lastMockTimer
}
}

Delay/Wait in a test case of Xcode UI testing

Asynchronous UI Testing was introduced in Xcode 7 Beta 4. To wait for a label with the text "Hello, world!" to appear you can do the following:

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
let exists = NSPredicate(format: "exists == 1")

expectationForPredicate(exists, evaluatedWithObject: label, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

More details about UI Testing can be found on my blog.

Wait 30 sec in Xcode UI Test

The simplest way is just sleeping the execution for the time being:

sleep(30)

However, in case you are expecting something to appear, it's better to use the built-in function for waiting for existence:

element.waitForExistence(30)

It doesn't fail if nothing appears, so if it's crucial part of your logic, you probably better to check it via expectation with timer:

let exists = NSPredicate(format: "exists == %@", true)
expectationForPredicate(exists, evaluatedWithObject: element, handler: nil)
waitForExpectationsWithTimeout(30, handler: nil)

How to wait in a XCTest for T seconds without timeout error?

You can use XCTWaiter.wait functions.

For example:

let exp = expectation(description: "Test after 5 seconds")
let result = XCTWaiter.wait(for: [exp], timeout: 5.0)
if result == XCTWaiter.Result.timedOut {
XCTAssert(<test if state is correct after this delay>)
} else {
XCTFail("Delay interrupted")
}

Testing timer which calls function in swift

You might want to use the wait(for:timeout:) method.

Keep an instance of XCTestExpectation in your test case:

let expectation = XCTestExpectation(description: "value not nil")

In your someFunc, fulfil that expectation:

expectation.fulfill()

And in your test method, you do:

let timer2 = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
self.someFunc()
}

wait(for: [expectation], timeout: 2)

iOS Unit Testing: Wait for Time Interval with Expectations

I've used XCTestExpectation in a number of projects for all sorts of async web and GCD stuff... AFAIK, something like the following is what seems to be the most common usage:

- (void)testExample {
// create the expectation with a nice descriptive message
XCTestExpectation *expectation = [self expectationWithDescription:@"The request should successfully complete within the specific timeframe."];

// do something asyncronously
[someObject doAsyncWithCompletionHandler:^(NSInteger yourReturnValue) {
// now that the async task it done, test whatever states/values you expect to be after this is
// completed
NSInteger expectedValue = 42;
XCTAssert(yourReturnValue == expectedValue, @"The returned value doesn't match the expected value.");

// fulfill the expectation so the tests can complete
[expectation fulfill];
}];

// wait for the expectations to be called and timeout after some
// predefined max time limit, failing the test automatically
NSTimeInterval somePredefinedTimeout = 3;
[self waitForExpectationsWithTimeout:somePredefinedTimeout handler:nil];
}

iOS TestCase for method which includes NS Timer

You need to enter the event loop for a period of time, so that the timer event can be processed. It is basically not possible to fully regression test code without doing this. Here is a simplified method:

// Wait inside the event loop for a period of time indicated in seconds

+ (void) waitFor:(NSTimeInterval)maxWaitTime
{
int numSeconds = (int) round(maxWaitTime);
if (numSeconds < 1) {
numSeconds = 1;
}

for ( ; numSeconds > 0 ; numSeconds--) @autoreleasepool {
const int maxMS = 1000;
const int incrMS = 1;
const double seconds = 1.0 / (maxMS / incrMS);

for (int ms = 0 ; ms < maxMS; ms += incrMS) @autoreleasepool {
// One pass through the run loop for each time interval
NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:seconds];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:maxDate];
}
}

return;
}

A more complex impl with a selector that can be called to return when a test condition is true:

+ (BOOL) waitUntilTrue:(id)object
selector:(SEL)selector
maxWaitTime:(NSTimeInterval)maxWaitTime
{
NSAssert(object, @"object is nil");
NSAssert(selector, @"selector is nil");
NSMethodSignature *aSignature = [[object class] instanceMethodSignatureForSelector:selector];
NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
[anInvocation setSelector:selector];
[anInvocation setTarget:object];

// Invoke test condition method once before the timing loop is entered, so that the
// event loop will not be entered if the condition is initially TRUE.

BOOL state;

[anInvocation invoke];
[anInvocation getReturnValue:&state];

if (state) {
return TRUE;
}

// The condition is FALSE, so enter the event loop and wait for 1 second
// each iteration through the loop. The logic below makes sure that the
// 1 second wait will be done at least once, even if wait time is less
// than a full second.

int numSeconds = (int) round(maxWaitTime);
if (numSeconds < 1) {
numSeconds = 1;
}

for ( ; numSeconds > 0 ; numSeconds--) @autoreleasepool {
NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
[[NSRunLoop currentRunLoop] runUntilDate:maxDate];

[anInvocation invoke];
[anInvocation getReturnValue:&state];

if (state) {
return TRUE;
}
}

return FALSE;
}


Related Topics



Leave a reply



Submit