0

I have an error that seems to be related to OCMockObject PartialMock. When mocking an object and stubbing a method I get this unrecognized selector error which I'm pretty sure is an order or casting issue. Here's my test

STV_StreamServer *server = [NSEntityDescription insertNewObjectForEntityForName:@"STV_StreamServer"inManagedObjectContext:context];
id mockServer = [OCMockObject partialMockForObject:server];
[[[mockServer stub] andReturnValue:@YES] localURLPresent];
[[[mockServer stub] andReturnValue:@NO] remoteURLPresent];

id mockSUT = [OCMockObject partialMockForObject:sut];


[[[mockSUT stub] andReturnValue:@YES] canLiveStream:nil];

sut.streamServer = mockServer;

NSError *err = [mockSUT checkStreamingPlayabilityForUser:[self getUser:NO]];

XCTAssertNil(err, @"An error occured when basic user tried local playback");

sut is a STV_MediaServer. The error I get is [STV_MediaServer-0xb39aba0-407898154.181220 setStreamServer:]: unrecognized selector sent to instance 0xb39aba0. So first off I see that the object type seems wrong since it now includes what looks like a memory location. This occurs when I mock my sut. I'm sure it's an order issue. Been googling for hours.

e1985
  • 6,239
  • 1
  • 24
  • 39
Alex Reynolds
  • 6,264
  • 4
  • 26
  • 42
  • Off topic comment: in ObjC the convention to inform abour errors is to return YES/NO and pass a NSError ** parameter that will be set in case of error. – e1985 Dec 05 '13 at 16:46
  • I thought that was convention for async methods. If you look at some low level c++ methods used they return NSErrors like many core audio methods. Do you have a link to the page where apple says which way to do it? Cuz I'm always looking to code more compliant – Alex Reynolds Dec 05 '13 at 17:52
  • I don't have any link to any Apple page but I cannot find any method returning a NSError in the Cocoa APIs. Think about, for example, the save: method in NSManagedObjectContext. – e1985 Dec 06 '13 at 10:50
  • I understand that if you put an exception breakpoint the app stops in sut.streamServer = mockServer;, right? – e1985 Dec 13 '13 at 10:43
  • No the app stops in a private method in the sut that is called from `checkStreamingPlayabilityForUser` that checks self.streamServer – Alex Reynolds Dec 13 '13 at 19:23
  • Tho object type you're seeing, ie. `STV_MediaServer-0xb39aba0-407898154.181220`, is the result of how OCMock implements partial mocks. In order to do its work OCMock has to create a subclass at runtime and change the class of the partially mocked object to that subclass. It adds a memory location and a timestamp to the original class name to create a unique class name. – Erik Doernenburg Sep 06 '15 at 13:19

2 Answers2

0

I have not reproduce your issue, but I think it is not possible to stub properties(the CoreData ones declared with @dynamic) in managed objects.

For this case you show, you can simply set the properties to the values you want - no need for stubs here.

e1985
  • 6,239
  • 1
  • 24
  • 39
  • I'm stubbing a method not a property but the error is the tested method accessing a property. If you look at the error you can see the object Type has been changed by ocmock – Alex Reynolds Dec 05 '13 at 17:57
  • You are stubbing a method that is generated at runtime (the property is declared with @dynamic). OCMock cannot stub that AFAIK, so just set the properties directly in your managed object - no need for a mock in this case. – e1985 Dec 06 '13 at 10:53
  • canLiveStream is not a generated and does not correspond to a property. There is no property canLiveStream. Also note the error has nothing to do with the mocked method but rather a property inside the called method. Since the sut object was already generated at runtime and then mocked the mock should have access to all dynamic properties. If you think this isnt possible can you suggest code for a working test so I can see if your solution works? – Alex Reynolds Dec 06 '13 at 19:56
0

You're getting this error because you're invoking the method on mockSUT. Due to the way CoreData generates property accessors OCMock can't copy their implementations to the partial mock and therefore they can't be found by the ObjC runtime.

When checkStreamingPlayabilityForUser eventually calls self.streamServer self is actually mockSUT and the method 'streamServer' cannot be found.

This would work fine if you simply configured the managed object the way you need it for this specific test.

FWIW you should never try to mock instances of NSManagedObject, the preferred way to perform these types of test is to simply create test objects in the unit test that fit the configuration you need.


Data driven tests with Core Data:

It will help if you create a subclass of SenTestCase or XCTestCase that can manage the CoreData bits for you. This test case subclass should make available an instance of NSManagedObjectContext for your test to use.

An actual test might look something like this:

@implementation PeopleViewControllerTest
  - (void)testSomething {
    NSMutableArray *people = [NSMutableArray new];
    [self.managedObjectContext performBlockAndWait:^{
      for (int i=0; i < 100; i++) {
        Person *p = [NSEntityDescription insertNewObjectForEntityForName:@"Person"
                                                  inManagedObjectContext:self.managedObjectContext];
        p.firstName = [NSString stringWithFormat:@"First%d", i];
        [people addObject:p];
      }
    }];
    //return the people
    PeopleViewController *pvc = ...;
    id mockPVC = [OCMockObject partialMockForObject:pvc];
    [[[mockPVC stub] andReturn:people] fetchedPeople];

    //make sure the view controller behaves properly with these 100 people
  }
@end

So instead of creating 100 mock instances of NSManagedObject we just create 100 actual objects.

We're not trying to test CoreData, just our logic thats built on top of some set of NSManagedObjects. Therefore creating concrete instances of NSManagedObject is fine, but they should be configured to exercise your application's logic.

i.e. if you wanted to check email address validation, you might have:

p.emailAddress = @"notvalid";

//later with some mock object [[[partialMock expect] andReturn:p] person];

ImHuntingWabbits
  • 3,827
  • 20
  • 27
  • I disagree with not mocking NSManagedObjects. In data driven apps these represent a large area of possible logic errors. You should test your app as close to real situations as possible. Do you have an example of how you'd write unit tests for controllers that manipulate and consume core data objects? I think OCMock should implement dynamic attribute accessor generation. It's not that hard. – Alex Reynolds Dec 31 '13 at 00:02
  • Whats wrong with just populating the CoreData objects to suite your tests? Its not that hard... – ImHuntingWabbits Dec 31 '13 at 02:09
  • Not sure I understand. doing a straight alloc init doesn't create the core data accessors so you cant set any properties. Do you have an example of a better way I could see since I'm always looking for better ways to TDD. – Alex Reynolds Dec 31 '13 at 20:33
  • Sure, I'll amend my answer to include an example. FWIW you should never alloc init a managed object either, it really needs to be linked to a managed object context. – ImHuntingWabbits Jan 01 '14 at 10:30