Example of method doing too much – unit testing

Leave a comment

Was working on a refactor of this method when I realized it was doing too much.

- (void)showAppropriateView
{
    if ([self isOffline]) {
        [self.viewManager showOfflineView];
    } else if (!self.nullstateScreenHasBeenDisplayed || [self searchBarHasNoText]) {
        [self.viewManager showNullstateView];
    } else if (self.errorHasOccurred) {
        [self.viewManager showErrorView];
    } else if (self.tracks.count > 0) {
        [self.collectionView reloadData];
        [self.viewManager showContentView];
    } else {
        [self.viewManager showNoResultsView];
        [self updateNoResultsSearchText];
    }
}

On the surface it doesn’t look that bad. Just figuring out what view to display, and then displaying it.

But there are really x2 things going on with this method. One is displaying the view. But the other is doing the other things as a result of that view being displayed.

Before breaking I had to pull out the non display view stuff. Then I was free to have another method focus on the view logic itself.

Final result looked like this.

- (void)showAppropriateView
{
    if (self.tracks.count > 0) {
        [self.collectionView reloadData];
    } else if (self.tracks.count == 0) {
        [self updateNoResultsSearchText];
    }

    [self.viewManager showAppropriateViewWhenOffline:[self isOffline]
                     nullstateScreenHasBeenDisplayed:!self.nullstateScreenHasBeenDisplayed
                                  searchBarHasNoText: [self searchBarHasNoText]
                                    errorHasOccurred:self.errorHasOccurred
                                          trackCount:self.tracks.count];
}

Unit testing tip – additional override

1 Comment

If you have have a method you want to test, but it does some stuff you like for convenience, and you don’t really want to mess for the sake of testing, create with some overrides and do your testing there.

+ (void)foo:(NSURLSessionConfiguration *)sessionConfiguration
{
    TokenManager *tokenManager = [[TokenManager alloc] init];
    AccessToken *accessToken = tokenManager.accessToken;

    [self addAuthorizationHeaderToSessionConfiguruation:sessionConfiguration token:accessToken];
}

+ (void)foo:(NSURLSessionConfiguration *)sessionConfiguration token:(AccessToken *)accessToken
{
    NSString *accessTokenHeaderValue = [NSString stringWithFormat:@"%@ %@", accessToken.tokenType, accessToken.token];
    sessionConfiguration.HTTPAdditionalHeaders = @{@"Authorization" : accessTokenHeaderValue};
}

The first method is the convenient public one.
The second is the one you can test and inject other things into.

Objective-C Unit Testing Tricks

Leave a comment

Getting to an objects innards through categories

Normally you only want to test an object entirely through it’s public API. But for those times when you need to access an objects innards (but would or can’t expose them), you can make them public by defining the inner method as a category in your unit test.

- (void)testAddNumberToStack {
    Calculator *calc = [Calculator new];
    [calc pushOperand:1.0];
    XCTAssertEqual(1, calc.stackCount); // private
}

Calculator.m

-(NSUInteger)stackCount {
    return [self.myStack count]; // private
}

CalculatorTest.m

@interface Calculator (Tests) // category
-(NSUInteger)stackCount;
@end

Alternative to partical mocks

For those times when you want to override a method in a class under test, declare a new class and override the method in your test.

@interface TestSubclass : ClassUnderTest
@end

@implementation TestSubclass

- (void)methodToOverride
{
    ...
}

...
// In the test
- (void)testSubclass
{
    ClassUnderTest *testObject = [TestSubclass new];

    XCTAssert(...);
}

@end

Objective-C – How to stub out an NSError unit test

Leave a comment

#import <XCTest/XCTest.h>

#import "Player.h"

// Mocking somethings (like NSError's) can be tricky in OCMockito.
// One alternative, if your mocks get too complex, is to simply manually stub.

// create a test class for the behaviour your want to stub,
// override the methods
// and then inject that into your SUT

// For example

// Test class we want to stub
@interface FakeHandler : Handler
- (BOOL)playTrack:(NSString *)track error:(NSError **)errorPtr;
- (BOOL)playAlbum:(NSString *)album error:(NSError **)errorPtr;
@end

@implementation FakeHandler

// Method / behaviour we want to override
- (BOOL)playTrack:(NSString *)track error:(NSError **)errorPtr {

    NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain";
    NSString *desc = NSLocalizedString(@"Unable to connect to network.", @"");
    NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };

    *errorPtr = [NSError errorWithDomain:domain
                                         code:-101
                                     userInfo:userInfo];

    return NO;
}

- (BOOL)playAlbum:(NSString *)album error:(NSError **)errorPtr {
    return NO;
}

@end


// Now we can use all of this in our test.

@interface PlayerStubTest : XCTestCase
@property (nonatomic, strong) Player *player;
@property (nonatomic, strong) Handler *fakeHandler;
@end

@implementation PlayerStubTest

- (void)setUp {
    [super setUp];
    self.fakeHandler = [FakeHandler new];
    self.player = [[Player alloc] initWithHandler:self.fakeHandler]; // Dependence inject our stub
}

- (void)testNetworkFailure {
    // Now when we call our play method, we can expect a failure and test that it bubbles up
    NSError *expectedError;
    [self.player playURL:@"track.xxx" error:&expectedError];
    XCTAssertNotNil(expectedError);
}

@end

How to stub a class for unit testing

Leave a comment

Here is an example of how to override an existing class and create a stub for your unit tests in objective-c.

PlayerStubTest.m

#import <XCTest/XCTest.h>

#import "Player.h"
#import "Handler.h"

@interface PlayerStubTest : XCTestCase
@property (nonatomic, strong) Player *player;
@property (nonatomic, strong) Handler *stubHandler;
@end

// overide default behavior of handler for those methds you want to test
@interface StubHandler : Handler
- (BOOL)playTrack:(NSString *)track;
- (BOOL)playAlbum:(NSString *)album;
@end


@implementation StubHandler

- (BOOL)playTrack:(NSString *)track {
 NSLog(@"Stub track");
 return NO;
}

- (BOOL)playAlbum:(NSString *)album {
 NSLog(@"Stub albumn");
 return NO;
}

@end

@implementation PlayerStubTest

- (void)setUp {
 [super setUp];
 self.stubHandler = [StubHandler new];
 self.player = [[Player alloc] initWithHandler:self.stubHandler];
}

- (void)testExample {
 [self.player playURL:@"track"];
}

@end

 

When you run this you should see ‘Stub track’ in the console.

 

Lessons learned in TDD

Leave a comment

This was an excellent talk Ian Cooper gave at NDC a couple years ago.
It hits on a lot of the challenges and common problems Ian and others have seen with test suites and TDD in general.

It’s a great talk. Which you can watch in it’s entirety here.

Here are some notes.

The Problem

Screen Shot 2016-02-16 at 8.54.34 AM

Where did it all go wrong

Screen Shot 2016-02-16 at 8.54.40 AM

Two keep points
Not writing tests against behaviors.
Coupling our implementation details to our tests.

If we fix these we will have smaller test suites, much more self explaining, and much less painful to own.

There was advice in early TDD that said when you are given a new method on a class, that was the trigger point for writing a new test.

And that’s really wrong.

The trigger for writing a new test is to have some piece of behavior that I want to implement. The test needs to capture that behavior.

The reason why when you go back to your tests that you find them so hard to read is there is a lost connection between the low level test you are writing and the high-level feature you are trying to solve. It’s lost in the noise.

What you need to do is express in your test that we are testing a given behavior of our system.

Before you get to the point where you are going to put the implementation details down, you first put down the behaviours you are trying to capture, and then the implementation. Don’t jump ahead too early.

Testing outside in

Start with the use case, the story, the scenario or feature we want to solve, on the outside, then work your way in from there.

Recommendation: don’t start this at the UI level. Start one level beneath at the plain old object level. Then if your UI changes, it won’t matter. Focus on the domain models.

Test the public API of classes. Not the internals. Test the surface only, and it should be quite narrow.

As soon as you start testing the internals you are coupling your tests to your implementation details. To change your implementation details you now have to change your tests. And that’s the key problem.

So your surface area should be much narrowly than many people are testing today. Just the API.

That means you will write less tests, tests against the use cases, and refactoring the contract remains the same, the internals change, and you don’t break any tests.

BDD

This is what BDD is all about. Testing behavior, and not low level details, and methods on classes.

What is a unit test

Screen Shot 2016-02-16 at 8.54.48 AM

The simplest thing

Screen Shot 2016-02-16 at 8.54.55 AM

The real problem with TDD is Kent asked us to go green as fast as possible, committing as many sins as we want in that step. So go to project, cut and paste some code, and stick in application. Hack it. Make it work. Don’t worry about the design. Get it green ASAP.

Kent’s point here is that it’s difficult to do two things at once. Make the test pass and design at the same time. Better to separate out.

Green is about solving the problem. Refactoring is about doing the design.

Screen Shot 2016-02-16 at 8.55.00 AM

You do not write new tests here when refactoring. You already have the high-level behavior tests written. So long as those pass, you are OK. If you need another test, write it at that level.

But when you are refactoring, you are free to focus on design and engineer it right.

If you write tests here you bind your implementation details to your tests.

Coupling is the biggest problem in software. It is the enemy. Forget DRY. Coupling will kill you. Do you couple tests to implementation details.

Screen Shot 2016-02-16 at 8.55.06 AM

Dependency is the key problem in software development at all scales.

We need to eliminate the dependency between our tests and our code. Tests should not depend on details. Tests should depend on contracts or public interfaces. This allows us to change implementations without changes tests.

Test behaviors not implementations.

Means less tests.
Moving faster.
Going quickly to green.
Now you are refactoring and making it cleaner.
Better forward progress, the tests aren’t slowing you down so much.

Screen Shot 2016-02-16 at 8.55.13 AM

iOS test classes not running

Leave a comment

If you’ve added unit tests to a new project and for some reason they aren’t running… try this.

Double click your blue project icon

Screen Shot 2015-11-13 at 11.18.26 AM

Click the Test Target, Build Phases, and expand Compile Sources

Screen Shot 2015-11-13 at 11.18.43 AM

Screen Shot 2015-11-13 at 11.23.55 AM

Click the ‘+’ sign then ‘Add other’ at the bottom, navigate to your files, and try adding them manually. May fix your problem.

Older Entries

%d bloggers like this: