jonreid / OCMockito

Mockito for Objective-C: creation, verification and stubbing of mock objects
MIT License
991 stars 118 forks source link

Argument captor capturing outer function #154

Closed nicoabie closed 4 years ago

nicoabie commented 5 years ago

This seems related to #123

I would like to test the following code:

- (id) subscribeTripUploadSuccess:(void(^)(NSString *tripId, NSData *data))callback {
    return [self.notificationCenter addObserverForName:@"tripUploadSuccess" object:self queue:nil usingBlock:^(NSNotification *note) {
        callback([note.userInfo valueForKey:@"tripId"], [note.userInfo valueForKey:@"data"]);
    }];
}

for that I have the following test :

- (void) testSubscribeTripUploadSuccess {
    NSData *dataMock = [NSData data];

    void (^callback)(NSString *, NSData *) = ^(NSString *tripId, NSData *data) {
        assertThat(tripId, equalTo(@"123456"));
        assertThat(data, equalTo(dataMock));
    };

    [notificationService subscribeTripUploadSuccess:callback];

    HCArgumentCaptor *captor = [[HCArgumentCaptor alloc] init];
    [verify(notificationCenterMockProtocol) addObserverForName:@"tripUploadSuccess" object:notificationService queue:nil usingBlock:(id)captor];

    NSNotification *noteMock = mock([NSNotification class]);
    [given([noteMock userInfo]) willReturn:@{ @"tripId": @"123456", @"data":dataMock}];

    void (^blockHandler)(NSNotification *note) = captor.value;

    blockHandler(noteMock);
}

This test fails because addObserverForName gets called twice.

If I jump into blockHandler(noteMock); I see one call pointing at the line which contains the call to addObserverForName with a null callback and on the next call it correctly points to the correct line callback([note.userInfo valueForKey:@"tripId"], [note.userInfo valueForKey:@"data"]); with the correct callback (different than null).

Relevant PODS:

jonreid commented 4 years ago

Hmm. Rather than diagnose the problem… I recommend not mocking NotificationCenter. Instead, just use it, and register the test suite as an observer to capture what is sent. I think you'll find the resulting test is easier to read.

Since NotificationCenter lives on outside the test run, you just have to make sure to remove the observer in tearDown.