13

When running an XCTest that executes a network operation, i get the following error:

-waitForExpectationsWithTimeout: timed out but was unable to run the timeout handler because the main thread was unresponsive (0.5 seconds is allowed after the wait times out). Conditions that may cause this include processing blocking IO on the main thread, calls to sleep(), deadlocks, and synchronous IPC. Xcode will attempt to relaunch the process and continue with the next test...

I have a test that executes a Network Command method which uses NSURLSession under the hood.

This test is part of a test class with 4 consecutive network request tests.

When run independently from other tests it succeeds every time. When running the entire Test file (4 tests in total), it succeeds every time.

But when running the entire test suite (50 tests), i get "Stall on main thread" on the first network operation test. The other 3 network operation tests go through fine.

Here is the code for the test:

- (void)test_session_001_ObtainsSessionObjectFromServer
{
    XCTestExpectation *expectation = [self expectationWithDescription:[self description]];
    
    id<IPVideoSessionProtocol> provider = [IPVideoSessionFactory getProvider];
    
    [provider createVideoSessionWithUserID:@"SomeUserID"
        withCompletionBlock:^(IPVideoSession *session) {
        
        if ([session isKindOfClass:[IPVideoSession class]] &&
            [session.sessionID isKindOfClass:[NSString class]] &&
            session.sessionID.length > 0) {
            
            // The returned session ID is populated
            [expectation fulfill];
        }
    }];
    
    [self waitForExpectationsWithTimeout:5.0f handler:^(NSError *error) {
        if (error)
        {
            XCTFail(@"IPVideoSessionManagerTests Did not execute in allotted time");
        }
    }];
}

The code inside createVideoSession...

- (void)createVideoSessionWithUserID:(NSString*)userID
    withCompletionBlock:(IPVideoSessionCompletionBlock)block
{
    IPCreateVideoSessionCommand* command = [[IPCreateVideoSessionCommand alloc]
        initWithUserID:userID];
    
    [command executeWithCompletionBlock:^(IPNetworkError *error, NSString *resultJSON)
    {
        if (error) {
            [IPAlertPresenter displayNetworkError:error];
            block(nil);
            return;
        }
        
        NSDictionary *dict = [resultJSON toJsonDictionary];
        block([IPVideoSession getSessionFromDictionary:dict]);
    }];
}

The code inside executeWithCompletionBlock:

- (void)executeWithCompletionBlock:(CommandCompletionBlock)block
{
    if (!self.isConnected && ![[IPDebugManager sharedInstance] networkBypassActivated]) {
        IPNetworkError* error = [[IPNetworkError alloc] initWithType:NetworkErrorTypeNotConnectedToNetwork];
        block(error, nil);
        return;
    }
    
    NSURLSession *session = [self composeSession];
    NSURLRequest *request = [self composeRequest];
    
    self.strongSelf = self;
    __weak __typeof(self) weakSelf = self;
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        [IPDispatch toMainThread:^{
            [weakSelf
                handleCommandResponse:response
                withData:data
                withError:error
                withCompletionBlock:block];

            weakSelf.strongSelf = nil;
        }];
    }];
    
    [dataTask resume];
}

NOTE: This error only happens when all tests are run. This test succeeds by itself, and also: if I run all 4 tests inside this Test Class.

Community
  • 1
  • 1
FranticRock
  • 3,233
  • 1
  • 31
  • 56
  • 1
    Networking tests are problematic in general—doubly so when you're bouncing code to the main thread. Without knowing what the IPDispatch's toMainThread method does, my first guess would be that some code in here is trying to synchronously dispatch to the main thread something that is already running on the main thread, and is blocking on itself. – dgatwood Aug 03 '16 at 03:49
  • Here's some additional exception information i'm getting: "timed out but was unable to run the timeout handler because the main thread was unresponsive (0.5 seconds is allowed after the wait times out)" Also, to add to your comment regarding IPDispatch: It has a check which checks if we are on Main thread already, and if so, just call the block directly, otherwise Dispatch onto the main thread. So the "Is Main Thread" check is already handled. – FranticRock Aug 08 '16 at 18:31
  • Another discovery: I am getting this error consistently on one Simulator iPhone 6 plus (GUID A) - every time. I switch to another simulator: iPhone 6 plus (GUID B), and the whole test suite works fine - every time. I tried Resetting both simulators completely, and did a clean. This doesn't make a difference. So test state pollution doesn't seem to be the issue. – FranticRock Aug 08 '16 at 19:02
  • Same iOS version on both? – dgatwood Aug 09 '16 at 04:52
  • Same iOS Version. Both 9.3. Consistent "Stall on main thread" on one, and not on the other. Cleaning, and purging simulator data does not help. Very strange. – FranticRock Aug 16 '16 at 17:53
  • 1
    Did you find any solution to this issue? I'm facing the same and is driving me nuts! – Nicolas Jakubowski Sep 22 '16 at 19:00
  • 1
    No i still get "Stall on Main Thread" in XCTest, when running async tests that use waitForExpectationsWithTimeout. In a lot of cases the problem only occurs, when i run the entire test suite. When running the test individually, it works. – FranticRock Dec 21 '16 at 17:57
  • Any updates on this? – Baby Groot Apr 19 '18 at 20:16
  • I never got a definitive answer on why this happened. It was however related to async tests, possibly contending over shared common resources. It made me write more strict unit tests which don't access global state, and verify pure functions that accept a single input and return a single output. I don't do view controller testing any more, because there are simply too many overlapping concurrent things that can go wrong. I try to avoid test pollution by not setting state, and mocking all state out instead, and I try to stay away from Async tests altogether. – FranticRock Apr 19 '18 at 21:30
  • @FranticRock I'm also facing a similar issue and I did not manage to solve it yet. Did you find out any other way to solve your issue besides the ones already mentioned above? – Luiz Durães Jan 10 '20 at 15:38

0 Answers0