-1

I am trying to do some unit testing for the API. Here is the actual function.

 func registerApi(path: String, player_id: Int, contest_id: Int, country_id: Int,  success: () -> Void)
{
    ServiceHelper.sharedInstance.sendRequest(path: "register-country",
                                             params: ["api_token": Constants.USER_INFO["api_token"].rawValue,
                                                      "player_id": player_id,
                                                      "country_id": country_id,
                                                      "contest_id": contest_id],
                                             showSpinner: true,
                                             completionHandler:
        { (response, error) in

            if (error != nil)
            {
             Test to be failed
            }
            else
            {
                Test Passed

            }
    })
}

And, now here is the test function for unit testing.

func testApiWorking()
{
    let controller = WorldCupChooseCountryVC()

    let expected = XCTestExpectation(description: "Some Countries to return")


    controller.registerApi(path: "get-country", player_id: 163, contest_id: 1, country_id: 1) { success in if success { expected.fulfill() }
    else{
        XCTFail()
        }
    }

    waitForExpectations(timeout: 1.0) { (_) -> Void in
    }

}

But, whenever I try to test this I get the following error.

[UIApplication applicationState] must be used from main thread only

Once, it also ran well, the test failed, but it ran. But, it is not even running now.

Rob
  • 2,086
  • 18
  • 25

2 Answers2

3

Main Thread Checker detects invalid usage of AppKit, UIKit and other APIs from background threads. completionHandlerseems to update UI so maybe move that part to a separate method and call it from using Dispatch Queue.

DispatchQueue.main.async {
     handleResponseOrError(response, error)
}
Mika
  • 1,256
  • 13
  • 18
1

(Question changed from being about the main thread to how to define and call closures. This answer has changed to reflect that.)

[UIApplication applicationState] is most likely being called from within the GeneralHelper.

The service helper is most likely making the network request on a background thread.

So, to make sure GeneralHelper is called on the main thread, execute it via a DispatchQueue.

(Also! You're not calling your completion handler, so we'll add that in too.)

func registerApi(path: String, player_id: Int, contest_id: Int, country_id: Int,  completion: ((Bool) -> Void)? = nil)
{
    let helper = ServiceHelper.sharedInstance
    let params = [
        "api_token": Constants.USER_INFO["api_token"].rawValue,
        "player_id": player_id,
        "country_id": country_id,
        "contest_id": contest_id
    ]
    helper.sendRequest(path: "register-country", params: params, showSpinner: true) { (response, error) in
        var success = true

        if let error = error
        {
            // Do stuff on failure.
            success = false
        }
        else
        {
            // Do stuff on success
        }

        // Call completion handler from 'registerApi'
        completion?(success)
    })
}

In your test, you can now add a parameter to test for success.

func testApiWorking()
{
    let controller = WorldCupChooseCountryVC()

    let expected = XCTestExpectation(description: "Some Countries to return")

    controller.registerApi(path: "register-country", player_id: 163, contest_id: 1, country_id: 1) { success in
        if success {
            // Test success
        } else {
            // Test fail
        }

        expected.fulfill()
    }

    waitForExpectations(timeout: 30) { (_) -> Void in
    }
}
ABeard89
  • 911
  • 9
  • 17
  • In the error and else case I just want to get that if error is not empty it will show in the unit test that something is wrong, else the function is working. – Rob May 29 '18 at 05:18
  • In that case, I would suggest changing the success handler to accept a boolean parameter indicating success or failure. I'll edit the answer to show this change. – ABeard89 May 29 '18 at 05:20
  • Added boolean parameter to completion handler. I also changed the name from `success` to `completion` because success or failure is in the parameter. – ABeard89 May 29 '18 at 05:29
  • I also changed the completion handler to an Optional. This is common practice and makes the method easier to call. – ABeard89 May 29 '18 at 05:30
  • I am getting this error: Cannot convert value of type '() -> Void' to expected argument type '((Bool) -> Void)?', when in unit test I passed this. controller.registerApi(path: "get-country", player_id: 163, contest_id: 1, country_id: 1) { expected.fulfill() } – Rob May 29 '18 at 05:46
  • That's because the closure now needs a parameter, just like the closure on `sendRequest` took two: `response` and `error`. Add in a parameter with whatever name you want, and it'll be fine. For example `{ success in }`. – ABeard89 May 29 '18 at 05:49
  • Then you can test for success in the closure. `{ success in if success { expected.fulfill() } }` – ABeard89 May 29 '18 at 05:50
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/171964/discussion-between-rob-and-abeard89). – Rob May 29 '18 at 06:01