Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this test robust? Can I use DispatchQueue.main.async() in a unit test?

Tags:

swift

Simplifying a bit the code structure, I have a UIViewController with viewDidLoad() that calls a method that uses DispatchQueue.main.async() to wait for the main thread to execute the code.

viewDidLoad() {
   method()
}

method() {
   ...
   DispatchQueue.main.async() {
      ...some code...
   }
}

My test also need to wait for the main thread before to call XCTAssertEqual.

func testSuccessRequest() {
        let exp = expectation(description: "labelText")
        let vc = ViewController.init()
        vc.request = SuccessRequest.init()
        vc.loadViewIfNeeded()

        DispatchQueue.main.async() {
            XCTAssertEqual(vc.label.text, "success")
            exp.fulfill()
        }

        waitForExpectations(timeout: 40, handler: nil)
}

Is this test robust? Or could I have situations in which the assert code is called before the async?

like image 630
aneuryzm Avatar asked Oct 11 '17 15:10

aneuryzm


1 Answers

Since method() is async so while testing if you call

DispatchQueue.main.async() {
            XCTAssertEqual(vc.label.text, "success")
            exp.fulfill()
        }

right after vc.loadViewIfNeeded() you are not guaranteed that test case expectation will get full fill before async method() calls gets completed. Since both calls - method() & above - are async.

But what you can do is - ful fill test case expectation after some time like below:

   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            XCTAssertEqual(vc.label.text, "success")
            [expectation fulfill];
            });

waitForExpectations(timeout: 40, handler: nil)

This is the one way you can test async method which is uses GCD. Hope it helps.

EDIT: While above strategy can make you wait for 2s. There is better method in this scenario we can implement.

We can take help of XCTKVOExpectation and XCTWaiter class available in XCTest framework. Here is the brief descriptions about them:

  • XCTKVOExpectation Cretes an expectation which is full filled by "keyPath" of an observed object
  • XCTWaiter Provides more descriptive return value when expectation timeout compare to normal waitForExpectations(timeout:, handler:) which simply throws error.

Now your test case will look like.

   func testSuccessRequest() {            
        let promise = XCTKVOExpectation(keyPath: "text", object: vc.label,
                                              expectedValue: "success")
        let vc = ViewController.init()
        vc.request = SuccessRequest.init()
        vc.loadViewIfNeeded()
        let result = XCTWaiter().wait(for: [promise], timeout: 40)
        XCTAssertTrue(result == .completed)
    }    

Here, a KVO exp. is created on "text" properties of label and then view is loaded with async call and then test case wait for some time before time out. Then you can assert the return value from XCTWaiter.

Hope it helps.

like image 189
manismku Avatar answered Sep 28 '22 07:09

manismku