0

It's stepping into the ViewDidLoad of the main view controller, and hitting the line calling get all tweets, but I put a breakpoint in the getAllTweets of both the base and derived to see if it just wasn't hitting the derived like I expected.

@implementation WWMainViewControllerTests {

    // system under test
    WWMainViewController *viewController;

    // dependencies
    UITableView *tableViewForTests;
    WWTweetServiceMock *tweetServiceMock;    
}

- (void)setUp {

    tweetServiceMock = [[WWTweetServiceMock alloc] init];
    viewController = [[WWMainViewController alloc] init];
    viewController.tweetService = tweetServiceMock;
    tableViewForTests = [[UITableView alloc] init];
    viewController.mainTableView = tableViewForTests;
    tableViewForTests.dataSource = viewController;
    tableViewForTests.delegate = viewController;
}

- (void)test_ViewLoadedShouldCallServiceLayer_GetAllTweets {

    [viewController loadView];
    STAssertTrue(tweetServiceMock.getAllTweetsCalled, @"Should call getAllTweets on tweetService dependency");
}

- (void)tearDown {

    tableViewForTests = nil;
    viewController = nil;
    tweetServiceMock = nil;
}

The base tweet service:

@implementation WWTweetService {

    NSMutableArray *tweetsToReturn;
}

- (id)init {

    if (self = [super init]) {
        tweetsToReturn = [[NSMutableArray alloc] init];
    }

    return self;
}
- (NSArray *)getAllTweets {

    NSLog(@"here in the base of get all tweets");
    return tweetsToReturn;
}

@end

The Mock tweet service:

@interface WWTweetServiceMock : WWTweetService

@property BOOL getAllTweetsCalled;

@end

@implementation WWTweetServiceMock

@synthesize getAllTweetsCalled;

- (id)init {

    if (self = [super init]) {
        getAllTweetsCalled = NO;
    }

    return self;
}

- (NSArray *)getAllTweets {

    NSLog(@"here in the mock class.");
    getAllTweetsCalled = YES;
    return [NSArray array];
}

The main view controller under test:

@implementation WWMainViewController
@synthesize mainTableView = _mainTableView;
@synthesize tweetService;

NSArray *allTweets;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    allTweets = [tweetService getAllTweets];
    NSLog(@"was here in view controller");
}

- (void)viewDidUnload
{
    [self setMainTableView:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}
Mark W
  • 3,879
  • 2
  • 37
  • 53
  • I'm a little suspicious of your `@synthesize tweetService`. Wondering if the property has been wired to `_tweetService` per the latest Xcode defaults. What happens if in `viewDidLoad` you call `[self.tweetService getAllTweets]`? – Carl Veazey Sep 28 '12 at 22:25
  • I'm having the same suspicions as Carl. See my full answer for more detail. – Nathan Eror Sep 28 '12 at 22:42
  • Thanks to both of you. I just don't understand why the ivar is not the same instance as the tweetService property? I guess my .NET corruption isn't letting me see why they would be different. Short answer, yes calling self.tweetService passes the test. – Mark W Sep 28 '12 at 23:57
  • @MarkW properties are shorthand for calling accessor methods (getters/setters). The instance variable that is retrieved or set in the accessor is really arbitrary. `@synthesize` will generate the code for the methods for you, using some default instance variable which it now also generates. With the last couple of Xcode versions, this has been `_propertyName`. Using accessors / properties is in my view absolutely the way to go unless you have a compelling reason not to. – Carl Veazey Sep 29 '12 at 04:27

1 Answers1

1

Since you're able to break in the debugger in viewDidLoad, what's the value of the tweetService ivar? If it's nil, the getAllTweets message will just be a no op. Maybe the ivar isn't being set properly or overridden somewhere else.

You should probably use the property to access the tweetService (call self.tweetService) rather than its underlying ivar. You should only ever access the ivar directly in getters, setters, and init (also dealloc if aren't using ARC for some crazy reason).

You also should not call loadView yourself, rather just access the view property of the view controller. That will kick off the loading process and call viewDidLoad.

Also, if you're doing a lot of mocking, I highly recommend OCMock.

Nathan Eror
  • 12,418
  • 2
  • 29
  • 19
  • Thanks Nathan, what I don't understand is when run the tests, the debugger steps into ViewDidLoad. Can you explain that? – Mark W Sep 28 '12 at 22:15
  • I totally got my answer wrong. That's what I get for answering on my phone. Ignore everything I said except for the part about OCMock and not calling loadView. I'm deleting the bad part of the answer as well. I don't want to lead other people down the wrong path. – Nathan Eror Sep 28 '12 at 22:21
  • Nathan can you explain why the ivar is only used in the init or getters setters (my understanding you only make your own getters and setters if the default ones don't suit you). – Mark W Sep 29 '12 at 00:03
  • 1
    It's not a hard and fast rule, but it is good defensive coding. Mike Ash had a good [Friday Q&A Post](http://www.mikeash.com/pyblog/friday-qa-2009-11-27-using-accessors-in-init-and-dealloc.html) about it. Be sure to read the comments to the post as well. – Nathan Eror Sep 30 '12 at 19:31