1

If I dispatch_async a block on main queue like this:

-(void) myTask {
  dispatch_async(dispatch_get_main_queue(), ^{
      [self.service fetchData];
   });
}

In unit test, I can execute the block passed in main queue by manually run the main loop like this:

-(void)testMyTask{
  // call function under test
  [myObj myTask];
  // run the main loop manually!
  [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
  // now I can verify the function 'fetchData' in block is called
  ...
}

Now, I have another similar function which dispatch block to an sequential queue other than main queue:

-(void) myTask2 {
  dispatch_async(dispatch_queue_create("my.sequential.queue", NULL), ^{
      [self.service fetchData];
   });
}

In unit test, how can I execute the block manually now?

-(void)testMyTask2{
  // call function under test
  [myObj myTask2];
  // How to manually execute the block now?
}

=== Clarify ===

The reason why I want to manually execute is because I don't like any Wait-For-Timeout way doing the test. Because waiting time is depending on CPU speed, on different machines could be different. I'd like to manually execute the block passed to queue (the same way as how I did for the main queue test case) and then verify the result.

Leem.fin
  • 40,781
  • 83
  • 202
  • 354
  • What are trying to test: network request or result of background task or something else? If you need to test just async call, then stub ```self.service fetchData``` call and call back from it immediately, then you can use solution with ```waitForExpectationsWithTimeout```provided here. It won't take long. – Konstantin Jun 23 '16 at 13:24
  • @Konstantin , it could be anything, network request, background task, etc. It is not the point of my question. I just want to know how to manually make the queue execute the block. That is my main concern. – Leem.fin Jun 23 '16 at 13:27
  • I don't think it's possible unless you create the queue in the test function and pass it over. That's probably not what you want though. – Code Jun 23 '16 at 13:28
  • @Code, do you know is there a NSRunLoop object in the queue (not main queue)? – Leem.fin Jun 23 '16 at 13:30
  • Use `currentRunLoop` instead of `mainRunLoop`. – Code Jun 23 '16 at 13:30
  • Test implementation depends on realization of your method. Is it GCD, NSThread, NSOperation ? – Konstantin Jun 23 '16 at 13:31
  • @Konstantin, it is GCD. – Leem.fin Jun 23 '16 at 13:32
  • @Code, but I think calling `currentRunLoop` in test case is still pointing to the main runloop, because test case is running in the main thread. – Leem.fin Jun 23 '16 at 13:40
  • @Leem.fin I mean if you have a reference to the background queue then you can do `dispatch_sync(queue)` with `currentRunLoop` inside. – Code Jun 23 '16 at 13:42
  • In GCD there are no guaranteed option to force call one block from queue. There can be case when two blocks in one queue. ```XCTestExpectation```was made specially for testing async blocks. You should use it. Calling runloop in tests makes your tests less readable and lead to errors (in case there could be more than one block in queue) – Konstantin Jun 23 '16 at 13:46
  • @Code, that sounds like a good idea for my question. Could you please make an answer, I will try it, and it works I will accept it. – Leem.fin Jun 23 '16 at 15:29
  • @Konstantin, thanks for your comment, nice to hear your opinion, but as my question here is about an alternative solution than using Wait-To-Timeout approach, so using XCTestExpectation is not the option for my question. – Leem.fin Jun 23 '16 at 15:30

2 Answers2

1

You could create the queue in your test function.

-(void) myTask2:(dispatch_queue_t*)queue {
    dispatch_async(*queue, ^{
        [self.service fetchData];
    });
}

-(void)testMyTask2{
    dispatch_queue_t queue = dispatch_queue_create("my.sequential.queue", NULL);
    [myObj myTask2:&queue];

    dispatch_sync(queue, ^{
    });
}

(Just realised currentRunLoop is not needed)

Code
  • 6,041
  • 4
  • 35
  • 75
  • where do I do the assertion then? Do you mean in the test function `dispatch_sync(queue, ^{//assertion here?});` it dispatch synchronisely to execute the task enqueued in the `dispatch_async` call in function under test, then in the block we enqueue the assertion part? – Leem.fin Jun 25 '16 at 10:07
  • @Leem.fin You do it after the `dispatch_sync`. – Code Jun 25 '16 at 10:09
  • but is it so that the purpose of `dispatch_sync(queue, ^{});` in test, is to wait the execution of enqueued task in function under test? If answer is YES, then, Could we think in the way that the `dispatch_sync(queue, ^{ASSERT()});` would enqueue `ASSERT()` as the next task to execute, so, I can actually assert inside the block like what I did in my previous comment? – Leem.fin Jun 25 '16 at 10:17
  • I mean in test code , if I do `dispatch_sync(queue, ^{ assertion_code });`, it should first execute the enqueued task from function under test, then execute the assertion code, do you think it is correct theoretically? – Leem.fin Jun 25 '16 at 10:19
  • @Leem.fin It should work, but the assertion would be running on a background thread so it has to be thread safe. – Code Jun 25 '16 at 10:28
  • thanks, actually, I tried it, it seems works, but I am not 100% sure, so I would like to hear your opinion. Yep, it is running in background thread, but I can dispatch again in that block to main queue to make the assertion to main thread, I mean this: `dispatch_sync(queue, ^{ dispatch_sync(mainQueue, ^{assertion_code}) })`, it should work as well, right? – Leem.fin Jun 25 '16 at 10:34
  • @Leem.fin That should cause the app to hang because there are 2 blocks trying to run on the main thread and waiting for each other. I'd say best to keep the `dispatch_sync` block empty and assert after that. – Code Jun 25 '16 at 10:45
  • OK, got it. Thanks. Another minor question: What would be the drawback if do assertion in background thread instead of main thread? – Leem.fin Jun 25 '16 at 10:50
  • @Leem.fin If the assertion code is not thread-safe then it may cause unexpected behaviors. If it is then you can do it in the background thread. – Code Jun 25 '16 at 10:57
0

For execute test in async block use XCTestExpectation class

-(void) myTask2 {
  XCTestExpectation *expectation = [self expectationWithDescription:@"catch is called"];
  dispatch_async(dispatch_queue_create("my.sequetial.queue", NULL), ^{
      [self.serviceClient fetchDataForUserId:self.userId];
      [expectation fulfill];
   });

   [self waitForExpectationsWithTimeout:Timeout handler:^(NSError *error) {
        //check that your NSError nil or not
    }];
}

Hope this help

iSashok
  • 2,326
  • 13
  • 21
  • That's exactly the reason why I ask how to manually execute the block, because I don't want to use `waitForExpectationsWithTimeout`, it looks bad to set a waiting time, because on different machine the waiting time could be different. I want to manually make the queue execute the block, then check the result, like how I did for the main queue. – Leem.fin Jun 23 '16 at 13:04
  • Please read the documentation "I don't like OCMock provided waitForExpectationsWithTimeout:Timeout" this comment is absolutely incorrect. OCMock not provided this methods ,`XCTestExpectation` class provided by Apple. – iSashok Jun 23 '16 at 13:09
  • I will update it, anyhow, I don't like any waiting for timeout way doing test, the reason is the same, the waiting time is unpredictable on different machines. – Leem.fin Jun 23 '16 at 13:11
  • I updated my words. Please don't downvote my question unless you have a good reason if it is downvoted by you. – Leem.fin Jun 23 '16 at 13:13
  • Your comment and arguments for wan't to use XCTestExpectation is not an argument for down vote my answer. – iSashok Jun 23 '16 at 13:13
  • I will undo the downvote, currently the system ask me to wait for 10min if I want to do that. – Leem.fin Jun 23 '16 at 13:15
  • When i wrote my answer your question have't any comment that you not prefer use XCTestExpectation – iSashok Jun 23 '16 at 13:15
  • I have undo my downvote. The reason why I clicked downvote is because when I mouse hover the downvote button the pop up says "this answer is not useful" which tells under what condition people can downvote. But yep, I didn't make my question clear enough. – Leem.fin Jun 23 '16 at 13:26