Lets assume we have a URLSessionClient implementation to fetch data from url and returns the promise back to the caller , in my case lets call it MainController
class URLSessionClient {
func requestWithURL(url: String) -> Promise<NSData>{
return NSURLSession.GET(url)
}
}
class MainController: UIViewController {
var urlSessionClient : URLSessionClient!
convenience init() {
self.init(urlSessionClient: nil)
}
init(urlSessionClient: URLSessionClient?) {
self.urlSessionClient = urlSessionClient
super.init(nibName: "MainController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let url = "http://jsonplaceholder.typicode.com/users"
let requestPromise = self.urlSessionClient.requestWithURL(url)
requestPromise.then { data -> Void in
print(“Promise resolved successfully”)
self.title = "my title"
}
.error{ error in
print(“Promise failed with reason \ (error)”)
}
}
}
In a ideal unit test environment , i would like to test this behaviour i.e what happens when promise is resolved successfully and what happens when it fails.
Lets consider a success scenario and set up the tests to verify the behaviour with the help of stubbing URLSessionClient to return a fake promise in tests(Please note i do not want to make network request in tests).
class ViewControllerSpec: QuickSpec {
override func spec() {
var viewController : MainController!
let mock = MockURLSessionClient()
var promise : Promise<NSData>!
beforeEach {
viewController = MainController(urlSessionClient: mock)
promise = Promise { fulfill, reject in
fulfill(NSData())
}
stub(mock) { stub in
when(stub.requestWithURL("http://jsonplaceholder.typicode.com/users")).thenReturn(promise)
}
XCTAssertNotNil(viewController.view)
}
context("When the view loads intially") {
it("it should make the service call to get the users") {
let mock = MockURLSessionClient()
verify(mock).requestWithURL("http://jsonplaceholder.typicode.com/users")
}
it("Make the tests green") {
expect(viewController.title).to(equal("my title"))
}
}
}
}
XCTAssertNotNil(viewController.view) will ensure MainController view did load is called and with the help of stub
let requestPromise = self.urlSessionClient.requestWithURL(url) will return the fake promise which is fulfilled with NSData().
Questions:
Although the promise is fulfilled then block is never executed in MainController. Why?
The then should definitely fulfill.
We have dozens of tests that prove that this works. Can you provide a test case so I can investigate?
@mxcl Here you go https://github.com/repl-ullas-ml/TestingQuick.git
When I run the project the then is called and the background turns yellow.
@mxcl Yes. Exactly my point. Promise resolves when you run. Problem i am facing is in regard to unit testing. Consider running cmd+U test fails
Promises are always asynchronous, you need to use XCTestExpectations or zalgo, see the FAQ.
@mxcl The fact i had to file an issue is because i could not get answers in FAQ and docs. Could you please give some code snippet or if could fix the tests directly in my repo would be super helpful.
Appreciate your help
I cannot, I know nothing about Quick I suggest you google for “asynchronous testing with Quick”.
When done you can contribute back to this project and improve the FAQ.
Google suggests something like:
expect(promise.value).toEventually(equal(something))
Closing due to no response.
@mxcl What i would actually want to implement is a way to defer the promise resolution and resolve it at a later point of time in tests. I strongly feel the solution lies somewhere in the way i am defining a promise and resolving it!
Perhaps i strongly feel pending promise is way to go
let (promise, fulfill, _) = Promise<NSData>.pendingPromise()
stub(mock) { stub in
when(stub.requestWithURL("http://jsonplaceholder.typicode.com/users")).thenReturn(promise)
}
UIApplication.sharedApplication().keyWindow!.rootViewController = viewController
XCTAssertNotNil(viewController.view)
In simple terms i am creating a promise and giving it back to the controller by stubbing.You can ensure the controller receives the pending promise which is yet to be resolved.
And after view is loaded i resolve the pending promise by fulfill(result). Now then block execution needs to trigger in controller isn't it?
I'm sorry I don’t understand your questions. Perhaps you can try to explain them more concisely with more example code?
URLSessionClient returns promise with data from service to Controller, and if the promise is resolved then print(“Promise resolved successfully”) will execute.
But in tests you wouldn't want to hit an actual service. Hence you mock on dummy URLSessionClient and stub to return an promise with some test NSData.
So i return an promise which is yet to be resolved through the stubbing(If you enable breakpoint in the controller, promise you stub and return from the tests should be same as the promise that is received by the controller) and i force the viewDidLoad by calling XCTAssertNotNil(viewController.view)
At this point controller has a reference to the unresolved promise waiting for some one to fulfil. Now in tests when you need to verify behaviour of the success scenario you can fulfil the pending promise by calling fulfil(someNSData).
Now you have actually resolved an pending promise and then block should be executed , and after which when you verify the title of the view should be "My Title"
@mxcl Can't think of an better articulation than this. If you need reference to updated code please use https://github.com/repl-ullas-ml/TestingQuick.git
Most helpful comment
I cannot, I know nothing about
QuickI suggest you google for “asynchronous testing with Quick”.When done you can contribute back to this project and improve the FAQ.