Friday, August 19, 2011

OCMock observerMock and unrecognized selector


I've been writing lots of unit tests for some new classes that use process wide NSNotifications.  I wanted to test these since I have not written NSNotification code often and this is a base class that a lot of future code is going to be built upon.

A handy way to test these are to use OCUnit with OCMock to create mock observers to verify that your notifications are being called.  Many others have talked about how to setup OCUnit, OCMock, and do Unit Testing in Xcode so I won't go there.

One article that did help me out with the OCMock observerMock usage was Alex Vollmer's Making Fun of Things with OCMock.  I used his example to build upon to write my mock observer tests.

I did run into a problem though while testing.  A test for a notification, let's call it MyCoolNotification, would pass and then a second test that tested this notification would fail.  The weird thing if I would see failures like:
  • [NSCFString handleNotification:]: unrecognized selector ...
  • [NSCFNumber handleNotification:]: unrecognized selector ...
These failures would be for the same call, same line of the test, but on different runs I would get different classes throwing exceptions due to the unrecognized selector.  What to do?

I put it together when I noticed the pattern, that the first test of usage of MyCoolNotification would pass but the second and remaining would fail.

The examples of the usage of [OCMockObject observerMock] left out one important NSNotification item.  Once you finish your test, don't forget to remove your mock from observing the notification.

Here is a more complete example of that I am talking about:
- (void)test_MyCoolNotification {
  id mock = [OCMockObject observerMock];
  [[NSNotificationCenter defaultCenter] addMockObserver:mock
                                                   name:MyCoolNotification
                                                 object:nil];
                                               
  [[mock expect] notificationWithName:MyCoolNotification object:[OCMArg any] userInfo:[OCMArg any]];

  MyCoolClass *coolio = [MyCoolClass create];
  [coolio someMethodThatFiresMyCoolNotification];

  [[NSNotificationCenter defaultCenter] removeObserver:mock];
  [mock verify];
}

By adding the removal of the object from the NSNotificationCenter you can now test the notification multiple times in multiple tests.