Wait for main queue being dispatched before continue unit test - swift

I have this method in a class :
func handleResp(_ dataGotten: Result<Response?, Error>) {
....
DispatchQueue.main.async {
....
GlobalStaticStruct.code = 7
...
}
}
}
And then in my unit testing I have this code :
func testhandleResp() {
subject = SomeClass()
subject.handleResp(successResponse)
XCTAssertNotNil(GlobalStaticStruct.code)
}
I need to be able to test that GlobalStaticStruct.code is not nil but with the code as it is the test completes before the main queue so the field is nil all the time.
I understand the use of expectations to overcome similar issues but in this case there is no handler I can hook up to.
How can I make my test to wait for the main method to get triggered and finished and THEN test the value of GlobalStaticStruct.code?
Thanks in advance!

Specifically for main queue, you can use the fact that it's a serial queue. So if you schedule your check to run on the same queue after handleResp, your check will pass. And you can use expectation to synch the test after that check:
func testhandleResp() {
subject = SomeClass()
subject.handleResp(successResponse)
let expectation = XCTestExpectation()
DispatchQueue.main.async { // scheduled after subject.handleResp
XCTAssertNotNil(GlobalStaticStruct.code)
expectation.fulfill()
}
wait(for: [expectation], timeout: 10.0)
}
If it was any other (possibly concurrent) thread, you could do the same, but schedule verification block to run later, using schedule(after:...

Related

Using Dispatch.main in code called from XCTestCase does not work

I have a function that is a async wrapper around a synchronous function.
The synchronous function is like:
class Foo {
class func bar() -> [Int] {
return [1,2,3]
}
class func asyncBar(completion: #escaping ([Int]) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let intArray = bar()
DispatchQueue.main.async {
completion(intArray)
}
}
}
}
When I call it from a XCTestCase completion does not run.
Is there some sort of perverse interaction between the way unit tests are done in XCode and the main thread?
I can find no documentation on the Web about this.
I have to use the main thread for the callback as it interacts with the Gui.
My test case looks something like:
func testAsyncBar() throws {
var run = true
func stopThisThing(ints: [Int]) {
run = false
}
Foo.asyncBar(completion: stopThisThing)
while run {
print ("Running...")
usleep(100000)
}
}
The busy loop at the end never stops.
Your test’s while loop will block the main thread (if the test runs on the main thread). As such, the closure dispatched via DispatchQueue.main.async can never run, and your run Boolean will never get reset. This results in a deadlock. You can confirm this by printing Thread.isMainThread or adding a test like dispatchPrecondition(condition: .onQueue(.main)).
Fortunately, unit tests have a simple mechanism that avoids this deadlock.
If you want unit test to wait for some asynchronous process, use an XCTestExpectation:
func testAsyncBar() throws {
let e = expectation(description: "asyncBar")
func stopThisThing(ints: [Int]) {
e.fulfill()
}
Foo.asyncBar(completion: stopThisThing)
waitForExpectations(timeout: 5)
}
This avoids problems introduced by the while loop that otherwise blocked the thread.
See Testing Asynchronous Operations with Expectations.

XCTest - Unable to understand / implement expectations in unit tests (to test aysnc code)

(NOTE - I'm developing for macOS, so please ... iOS-specific advice won't help me)
What I'm trying to do:
I have an app component that performs a short task on a background thread, and then, if certain conditions are met, asynchronously sends out a notification on the main thread.
NOTE - I am not using NSNotification in my app code. I am using my own custom notification mechanism. So, any solution related to NSNotification is not applicable to me.
I'm writing a unit test for the above mentioned app component, and simply want to check if that notification was indeed sent or not. My test has to be able to wait a second or so to give the notification time to reach its subscriber/observer, before performing an assertion.
I want to be able to test both possible cases in my tests: Both are normal scenarios.
Notification was sent.
Notification was not sent.
After hours of reading several docs and code samples, I don't understand how to achieve this with expectations.
I just want to wait one second in my test. Is it really this complicated ?
sleep() doesn't work
DispatchQueue.main.asyncAfter(time) doesn't work
Timer doesn't work
Here's the app component that needs to be tested, and its unit test:
In the below code, where do I put expectation.fulfill() ???
class ComponentBeingTested {
func methodBeingTested() {
doSomeWork()
if certainConditionsAreMet {
DispatchQueue.main.async {sendOutNotification()}
}
}
}
...
class UnitTestForComponentBeingTested: XCTestCase {
let objectBeingTested = ComponentBeingTested()
func testMethodBeingTested() {
let expectation = self.expectation(description: "Notification was sent")
// Call the code being tested
objectBeingTested.methodBeingTested()
// How do I do this with expectations ??? Where does expectation.fulfill() go ?
waitForOneSecond()
XCTAssertTrue(notificationSent) // Assume the value of notificationSent is available
}
}
Here is an approach
func testMethodBeingTested() {
// create expectation
let expectation = self.expectation(description: "Notification was sent")
// set expectation condition
var notificationSent = false
let observer = NotificationCenter.default
.addObserver(forName: _Your_Notification_Name, object: nil, queue: nil) { _ in
notificationSent = true
expectation.fulfill()
}
// Call the code being tested
objectBeingTested.methodBeingTested()
// wait for expectation
self.wait(for: [expectation], timeout: 5)
XCTAssertTrue(notificationSent)
}
Check out XCTNSNotificationExpectation, which becomes fulfilled when a matching notification is posted. Different initializers are available, depending on how restrictive you want to be on the fulfilment of the expectation.
To check that the notification is not sent, set isInverted to true on the expectation object.
Then just add a call to waitForExpectations(timeout:handler:) at the end of your test.
Ok, after a lot of trial and error, this works great for me:
Description: I basically created a helper function in my test case class that contains all the boilerplate expectation/wait code. It does the following:
1 - Creates an expectation (i.e. XCTestExpectation) as a formality.
2 - Calls my (arbitrary) test case assertion code (passed in as a closure) on some global queue thread after the intended delay period. Once this assertion code has completed, the expectation is fulfilled (again, a formality).
3 - Waits on the expectation by calling XCTestCase.wait(timeout). This ensures that the main thread / run loop is kept alive while my assertion code completes on this other thread.
Then, in my test case, I simply invoke that helper function, providing it with a wait period and some code to execute (i.e. my assertions).
This way, I have a simple and expressive reusable function that hides all the excessive ugliness of expectations which I never thought necessary in the first place.
I can put this helper in a base class like MyAppTestCase: XCTestCase, so that it is available to all my test case classes.
NOTE - This solution can be enhanced and made even more generic/reusable, but as of now, this is quite sufficient for the purposes of the originally posted problem.
Solution:
class ComponentBeingTested {
func methodBeingTested() {
doSomeWork()
if certainConditionsAreMet {
DispatchQueue.main.async {sendOutNotification()}
}
}
}
...
class UnitTestForComponentBeingTested: XCTestCase {
let objectBeingTested = ComponentBeingTested()
// Helper function that uses expectation/wait to execute arbitrary
// test code (passed in as a closure) after some delay period.
func executeAfter(_ timeSeconds: Double, _ work: (#escaping () -> Void)) {
let theExpectation = expectation(description: "some expectation")
// Execute work() after timeSeconds seconds
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + timeSeconds) {
// The call to work() will execute my test assertions
work()
// As a formality, fulfill the expectation
theExpectation.fulfill()
}
// Wait for (timeSeconds + 1) seconds to give the work() call
// some time to perform the assertions
wait(for: [theExpectation], timeout: timeSeconds + 1)
}
func testMethodBeingTested() {
// Call the code being tested
objectBeingTested.methodBeingTested()
// Call the helper function above, to do the waiting before executing
// the assertions
executeAfter(0.5) {
// Assume the value of notificationSent is computed elsewhere
// and is available to assert at this point
XCTAssertTrue(notificationSent)
}
}
}

Wait for Delegate callback to finish the function in Swift

I have a 3rd-party SDK where architecture is like this:
I call a method of the service (lets call it TheirService)
Once that method completes, a delegate of that service (lets call it TheirServiceDelegate will receive a notification onSuccess if call succeeds or onFailure if it fails.
The problem with that is that methods of TheirService are dependant on each other, so I end up having a "chain reaction", where each next method of TheirService is called from previous callback.
Note that TheirService is single threaded, and is really picky about previous method to be complete before I can start the next one.
Currently my interactions with the service look like this:
protocol MyClientListener {
notifyOnFailure()
notifyOnResult(result: SomeObject)
}
class MyClient: TheirServiceDelegate {
static let instance = MyClient()
let myService = TheirService()
var myResult: SomeObject?
var resultListener: MyClientListener
private init() { }
func start(resultListener: MyClientListener) {
self.resultListener = resultListener
myService.initialize()
}
// TheirServiceDelegate method
func onInitializeSuccess() {
myService.bootstrap()
}
// TheirServiceDelegate method
func onBootstrapSuccess() {
myService.login(user, password)
}
// TheirServiceDelegate method
func onLoginSuccess() {
myService.doSomethingUsefulStep1()
}
// TheirServiceDelegate method
func onDoSomethingUsefulStep1Success() {
myService.doSomethingUsefulStep2()
}
// TheirServiceDelegate method
func onDoSomethingUsefulStep2Success(result: SomeObject) {
// ah, look, now I have some object I actually wanted!
resultListener.notifyOnResult(result)
}
}
I also have to deal with failure case for each of them. And I cannot skip or change the order of steps, which creates some sort of awkward state machine.
Instead i would like to interact with service via logical functions, that complete certain stages of the process from end to end, waiting for results between the steps:
class MyClient {
static let instance = MyClient()
let myService = MyService()
private init() { }
func connect() throws {
myService.initialize()
// wait for success or failure, throw on failure
myService.bootstrap()
// wait for success or failure, throw on failure
myService.login(user, password)
// wait for success or failure, throw on failure
}
func doSomethingUseful() -> SomeObject {
myService.doSomethingUsefulStep1()
// wait for success or failure, throw on failure
myService.doSomethingUsefulStep2()
// wait for success or failure, throw on failure
// on success, it will get an object it could return
}
Called like this:
try MyClient.instance.connect()
let x = try MyClient.instance.doSomethingUseful()
So is there any way to turn "wait for success or failure" comment into actual code that waits for that single-threaded service to call back? And where a delegate would fit in that case?

Race condition in unit tests

I'm currently testing a number of classes that do network stuff like REST API calls, and a Realm database is mutated in the process. When I run all the different tests I have at once, race conditions appear (but of course, when I run them one by one, they all pass). How can I reliably make the tests pass?
I have tried to call the mentioned functions in a GCD block like this:
DispatchQueue.main.async {
self.function.start()
}
One of my tests are still failing, so I guess the above didn't work. I have enabled Thread Sanitizer and it reports, from time to time, that race conditions appear.
I can't post code, so I'm looking for conceptual solutions.
Typically some form of dependency injection. Be it an internally exposed var to the DispatchQueue, a default argument in a function with the queue, or a constructor argument. You just need some way to pass a test queue that dispatches the event when you need to.
DispatchQueue.main.async will schedule the block async to the callee on the main queue and therefore isn't guarenteed by the time you make an assertion.
Example (disclaimer: I'm typing from memory so it might not compile but it gives the idea):
// In test code.
struct TestQueue: DispatchQueue {
// make sure to impement other necessary protocol methods
func async(block: () -> Void) {
// you can even have some different behavior for when to execute the block.
// also you can pass XCTestExpectations to this TestQueue to be fulfilled if necessary.
block()
}
}
// In source code. In test, pass the Test Queue to the first argument
func doSomething(queue: DispatchQueue = DispatchQueue.main, completion: () -> Void) {
queue.async(block: completion)
}
Other methods of testing async and eliminating race conditions revolve around craftily fulfilling an XCTestExpectation.
If you have access to the completion block that is eventually invoked:
// In source
class Subject {
func doSomethingAsync(completion: () -> Void) {
...
}
}
// In test
func testDoSomethingAsync() {
let subject = Subject()
let expect = expectation(description: "does something asnyc")
subject.doSomethingAsync {
expect.fulfill()
}
wait(for: [expect], timeout: 1.0)
// assert something here
// or the wait may be good enough as it will fail if not fulfilled
}
If you don't have access to the completion block it usually means finding a way to inject or subclass a test double that you can set an XCTestExpectation on and will eventually fulfill the expectation when the async work has completed.

Why my NSOperation is not cancelling?

I have this code to add a NSOperation instance to a queue
let operation = NSBlockOperation()
operation.addExecutionBlock({
self.asyncMethod() { (result, error) in
if operation.cancelled {
return
}
// etc
}
})
operationQueue.addOperation(operation)
When user leaves the view that triggered this above code I cancel operation doing
operationQueue.cancelAllOperations()
When testing cancelation, I'm 100% sure cancel is executing before async method returns so I expect operation.cancelled to be true. Unfortunately this is not happening and I'm not able to realize why
I'm executing cancellation on viewWillDisappear
EDIT
asyncMethod contains a network operation that runs in a different thread. That's why the callback is there: to handle network operation returns. The network operation is performed deep into the class hierarchy but I want to handle NSOperations at root level.
Calling the cancel method of this object sets the value of this
property to YES. Once canceled, an operation must move to the finished
state.
Canceling an operation does not actively stop the receiver’s code from
executing. An operation object is responsible for calling this method
periodically and stopping itself if the method returns YES.
You should always check the value of this property before doing any
work towards accomplishing the operation’s task, which typically means
checking it at the beginning of your custom main method. It is
possible for an operation to be cancelled before it begins executing
or at any time while it is executing. Therefore, checking the value at
the beginning of your main method (and periodically throughout that
method) lets you exit as quickly as possible when an operation is
cancelled.
import Foundation
let operation1 = NSBlockOperation()
let operation2 = NSBlockOperation()
let queue = NSOperationQueue()
operation1.addExecutionBlock { () -> Void in
repeat {
usleep(10000)
print(".", terminator: "")
} while !operation1.cancelled
}
operation2.addExecutionBlock { () -> Void in
repeat {
usleep(15000)
print("-", terminator: "")
} while !operation2.cancelled
}
queue.addOperation(operation1)
queue.addOperation(operation2)
sleep(1)
queue.cancelAllOperations()
try this simple example in playground.
if it is really important to run another asynchronous code, try this
operation.addExecutionBlock({
if operation.cancelled {
return
}
self.asyncMethod() { (result, error) in
// etc
}
})
it's because you doing work wrong. You cancel operation after it executed.
Check this code, block executed in one background thread. Before execution start – operation cancel, remove first block from queue.
Swift 4
let operationQueue = OperationQueue()
operationQueue.qualityOfService = .background
let ob1 = BlockOperation {
print("ExecutionBlock 1. Executed!")
}
let ob2 = BlockOperation {
print("ExecutionBlock 2. Executed!")
}
operationQueue.addOperation(ob1)
operationQueue.addOperation(ob2)
ob1.cancel()
// ExecutionBlock 2. Executed!
Swift 2
let operationQueue = NSOperationQueue()
operationQueue.qualityOfService = .Background
let ob1 = NSBlockOperation()
ob1.addExecutionBlock {
print("ExecutionBlock 1. Executed!")
}
let ob2 = NSBlockOperation()
ob2.addExecutionBlock {
print("ExecutionBlock 2. Executed!")
}
operationQueue.addOperation(ob1)
operationQueue.addOperation(ob2)
ob1.cancel()
// ExecutionBlock 2. Executed!
The Operation does not wait for your asyncMethod to be finished. Therefore, it immediately returns if you add it to the Queue. And this is because you wrap your async network operation in an async NSOperation.
NSOperation is designed to give a more advanced async handling instead for just calling performSelectorInBackground. This means that NSOperation is used to bring complex and long running operations in background and not block the main thread. A good article of a typically used NSOperation can be found here:
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
For your particular use case, it does not make sense to use an NSOperation here, instead you should just cancel your running network request.
It does not make sense to put an asynchronous function into a block with NSBlockOperation. What you probably want is a proper subclass of NSOperation as a concurrent operation which executes an asynchronous work load. Subclassing an NSOperation correctly is however not that easy as it should.
You may take a look here reusable subclass for NSOperation for an example implementation.
I am not 100% sure what you are looking for, but maybe what you need is to pass the operation, as parameter, into the asyncMethod() and test for cancelled state in there?
operation.addExecutionBlock({
asyncMethod(operation) { (result, error) in
// Result code
}
})
operationQueue.addOperation(operation)
func asyncMethod(operation: NSBlockOperation, fun: ((Any, Any)->Void)) {
// Do stuff...
if operation.cancelled {
// Do something...
return // <- Or whatever makes senes
}
}