Unit testing is just something I never seem to be able to get my head around but I can see why its important and can be a huge time saver (if you know what you're doing). I am hoping that someone can point me in the right direction.
I have the following UIViewController
QBElectricityBaseVC.h
@interface QBElectricityBaseVC : QBStateVC
@property (nonatomic, strong) QBElectricityUsage *electricityUsage;
@property (nonatomic, assign) CGFloat tabBarHeight;
- (void)updateElectricityUsage;
@end
QBElectricityBaseVC.m
@implementation QBElectricityBaseVC
- (instancetype)init
{
self = [super init];
if (self) {
self.tabBarItem = [[UITabBarItem alloc] initWithTitle:NSLocalizedString(@"electricity_title", nil) image:nil tag:0];
}
return self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.notificationCenter addObserver:self selector:@selector(updateElectricityUsage)
name:kUpdatedElectricityUsageKey object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.notificationCenter removeObserver:self];
}
- (void)updateElectricityUsage
{
self.electricityUsage = [self.stateManager electricityUsage];
}
- (CGFloat)tabBarHeight
{
return self.tabBarController.tabBar.frame.size.height;
}
@end
What should I test?
kUpdatedElectricityUsageKey
is addedself.electricityUsage
becomes an instance of QBElectricityUsage
tabBarHeight
is returnedkUpdatedElectricityUsageKey
is removedAm I missing anything I should test or testing something I really shouldn't?
How do I test?
So I am trying to do this using Specta and Expexta. If I need to mock anything I would be using OCMockito.
I really don't know how to test the observer is added/removed. I see the following in the Expexta documentation but not sure if its relevant/how to use it:
expect(^{ /* code */ }).to.notify(@"NotificationName"); passes if a given block of code generates an NSNotification named NotificationName.
expect(^{ /* code */ }).to.notify(notification); passes if a given block of code generates an NSNotification equal to the passed notification.
To test that self.electricityUsage
becomes an instance of QBElectricityUsage
I could create a category that has a method that just pretends the notification fired and calls the updateElectricityUsage
method but is this the best way?
And as for the tabBarHeight
, should I just test that it returns a valid CGFloat
and not worry what the value is?
UPDATE
I changed my viewWillAppear
method to look like below:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self addNotificationObservers];
}
- (void)addNotificationObservers
{
[self.notificationCenter addObserver:self selector:@selector(updateElectricityUsage)
name:kUpdatedElectricityUsageKey object:nil];
}
And then I created the following test:
#import "Specs.h"
#import "QBElectricityBaseVC.h"
#import "ElectricityConstants.h"
SpecBegin(QBElectricityBaseVCSpec)
describe(@"QBElectricityBaseVC", ^{
__block QBElectricityBaseVC *electricityBaseVC;
__block NSNotificationCenter *mockNotificationCenter;
beforeEach(^{
electricityBaseVC = [QBElectricityBaseVC new];
mockNotificationCenter = mock([NSNotificationCenter class]);
electricityBaseVC.notificationCenter = mockNotificationCenter;
});
afterEach(^{
electricityBaseVC = nil;
mockNotificationCenter = nil;
});
it(@"should have a notification observer for updated electricity usage", ^{
[electricityBaseVC addNotificationObservers];
[verify(mockNotificationCenter) addObserver:electricityBaseVC selector:@selector(updateElectricityUsage)
name:kUpdatedElectricityUsageKey object:nil];
});
});
SpecEnd
That test now passes but is this the correct/best way to test this?
You've just felt one big con of iOS ViewControllers: they suck at testability.
Another big problem with MVC is that it discourages developers from writing unit tests. Since view controllers mix view manipulation logic with business logic, separating out those components for the sake of unit testing becomes a herculean task. A task that many ignore in favour of… just not testing anything.
Article - source
Maybe you should think about using MVVM instead. This is a great article explaining the difference of iOS MVC and MVVM.
The great thing about using MVVM is that you can use DataBinding using Reactive Cocoa. Here's a tutorial that will explain Data Binding with MVVM and reactive programming in iOS.
I follow 2 practices for testing the pieces of a UIViewController.
Between those 2 systems I'm able to unit-test pretty much everything and then very easily write the UI tests to cover the final parts.
Also, quick note on your code: I wouldn't add your observers in viewWillAppear because it is called more than once. However, it may not be an issue since you probably won't get redundant calls to your handler because of notification coalescing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With