Unit test for a method that calls Async methods

I guess I have these lines of code:

func reset() { initializeAnythingElse() { // AnythingElse } initializeHomeData() { // HomeData } } func initializeHomeData(callback: @escaping (()-> Void)) { getHomeConfig() { callback() } } func initializeAnythingElse(callback: @escaping (()-> Void)) { getAnythingElse() { callback() } } 

and I would like to write unit test for this code. For initializeHomeData and initializeAnythingElse I can write unit test as:

 func testInitializeHomeData() { let successExpectation = expectation(description: "") sut.initializeHomeData { successExpectation.fulfill() } waitForExpectations(timeout: 1.0, handler: nil) // Validation goes here } func testInitializeAnythingElse() { let successExpectation = expectation(description: "") sut.initializeAnythingElse { successExpectation.fulfill() } waitForExpectations(timeout: 1.0, handler: nil) // Validation goes here } 

My question is: how to test reset() ? Should I just call them inside testReset() , for example:

 func testReset() { testInitializeHomeData() testInitializeAnythingElse() } 

but I think that is not a suitable implementation for this.

+5
source share
3 answers

You're right. To test reset you need to call reset , not internal methods.

At the same time, reset is currently written in such a way that makes it unstable. The reason you can easily test other standalone methods is the callback argument, which accepts.

I would recommend that you rewrite reset to allow two optional callbacks as follows:

 typealias Callback = () -> () func reset( homeDataCallback: @escaping Callback? = nil, anythingElseCallback: @escaping Callback? = nil) { initializeAnythingElse() { anythingElseCallback?() } initializeHomeData() { homeDataCallback?() } } 

Note that this change allows you to be notified in async when these two internal calls are completed.

Now your test method should be written using some kind of synchronization primitive, since a logical reset is performed only when both the home data and everything else is done, and their callbacks are called.

There are many ways to achieve this, but I will show you the semaphore approach:

 func testReset() { let expectation = expectation(description: "reset() completes within some duration") // some mechanism to synchronize concurrent tasks // I am using a semaphore let s = DispatchSemaphore(value: 0) let homeCallback: Callback = { s.signal() // signals the completion of home data init } let anythingElseCallback: Callback = { s.signal() // signals the completions of anything else setup } // call your reset method as part of the test reset(homeDataCallback: homeCallback, anythingElseCallback: anythingElseCallback) // we know we need to wait for two things to complete // init home data and anything else, so do that s.wait() s.wait() // at this step, reset internal async methods // have completed so we can now // fulfill the expectation expectation.fulfill() } 

Note that all of this change is required to allow you to test the reset call. Your function signature allows you to write reset() as current in existing code, as it has optional arguments that have default values ​​for default values.

+7
source

Add a callback to the reset method:

 func reset(callback: @escaping (()-> Void)) { var callbacksCount = 0 func tryCallback() { if callbacksCount == 2 { callback() } } initializeAnythingElse() { callbacksCount += 1 } initializeHomeData() { callbacksCount += 1 } } 

and test it the same way as for other methods.

You can also make calls sequentially, parallel execution is not like optimization, which costs more complex code:

 func reset(callback: @escaping (()-> Void)) { initializeAnythingElse() { initializeHomeData() { callback() } } } 
0
source

Go and check out XCTestExpectation .

This is the recommended api for asynchronous testing. Steps -

  • Create an instance of XCTestExpectation

  • Invocation of an asynchronous method

  • Call waitForExpectations(timeout: timeout, handler: handler)

  • Step 3 provides a timeout to complete the asynchronous call. Whenever you call the completion of your asynchronous call method, call .fulfill() on an XCTestExpectation instance

For reference -

XCTest and Asynchronous Testing in Xcode 6

Paste the code above the answer here, if the link stops working later -

 func testFetchNews() { let expectation = self.expectationWithDescription("fetch posts") Post.fetch(.Top, completion: {(posts: [Post]!, error: Fetcher.ResponseError!) in XCTAssert(true, "Pass") expectation.fulfill() }) self.waitForExpectationsWithTimeout(5.0, handler: nil) } 

This will help.

0
source

Source: https://habr.com/ru/post/1269888/


All Articles