Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent update block called on first time app installed. #25

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions MTMigration/MTMigration.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ typedef void (^MTExecutionBlock)(void);

+ (void) applicationUpdateBlock:(MTExecutionBlock)updateBlock;

/**

Executes a block of code for every time the application version changes.

@param updateBlock A block object to be executed when the application version changes. This parameter can't be nil.
@param ignoreFirstInstall Set to YES to not run the updateBlock when no app version is found. This is useful to avoid calling the updateBlock on first time install. Calling with NO, will have same effect as calling applicationUpdateBlock:(MTExecutionBlock)updateBlock;
*/

+ (void) applicationUpdateBlock:(MTExecutionBlock)updateBlock ignoreFirstInstall:(BOOL)ignoreFirstInstall;

/**

Executes a block of code for every time the application build number changes.
Expand Down
26 changes: 21 additions & 5 deletions MTMigration/MTMigration.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,29 @@ + (void) migrateToBuild:(NSString *)build block:(MTExecutionBlock)migrationBlock


+ (void) applicationUpdateBlock:(MTExecutionBlock)updateBlock {
if (![[self lastAppVersion] isEqualToString:[self appVersion]]) {
updateBlock();

[self applicationUpdateBlock:updateBlock ignoreFirstInstall:NO];
}

#if DEBUG
+ (void) applicationUpdateBlock:(MTExecutionBlock)updateBlock ignoreFirstInstall:(BOOL)ignoreFirstInstall {

BOOL lastAppVersionExists = ![[self lastAppVersion] isEqualToString:@""];
BOOL performCheck = lastAppVersionExists || !ignoreFirstInstall;

if(performCheck) {

BOOL appVersionDifferent = ![[self lastAppVersion] isEqualToString:[self appVersion]];
if (appVersionDifferent) {
updateBlock();

#if DEBUG
NSLog(@"MTMigration: Running update Block for version %@", [self appVersion]);
#endif

#endif

[self setLastAppVersion:[self appVersion]];
}
} else {

[self setLastAppVersion:[self appVersion]];
}
}
Expand Down
136 changes: 123 additions & 13 deletions MTMigrationTests/MTMigrationTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,46 @@

#import "MTMigrationTests.h"
#import "MTMigration.h"
#import <objc/runtime.h>

#define kDefaultWaitForExpectionsTimeout 2.0
#define kDefaultWaitForExpectionsTimeout 2.0
#define kBundleShortVersionStringKey @"CFBundleShortVersionString"
#define kBundleVersionKey @"CFBundleVersion"

@implementation MTMigrationTests

#define makr - Setup/TearDown

- (void)setUp {

[super setUp];
[self setUpMockBundle];
[MTMigration reset];
}

- (void)testMigrationReset
{
- (void)setUpMockBundle {

[self swapOutBundleMethods];
}

- (void)tearDown {

[self tearDownMockBundle];
[super tearDown];
}

- (void)tearDownMockBundle {

[self swapInBundleMethods];
[self setMainBundleAppBuild:nil];
[self setMainBundleAppVersion:nil];
}

#pragma mark - Test migrateToVersion

- (void)testMigrationReset {

[self setMainBundleAppVersion:@"1.0"];

XCTestExpectation *expectingBlock1Run = [self expectationWithDescription:@"Expecting block to be run for version 0.9"];
[MTMigration migrateToVersion:@"0.9" block:^{
Expand All @@ -47,8 +74,9 @@ - (void)testMigrationReset
[self waitForAllExpectations];
}

- (void)testMigratesOnFirstRun
{
- (void)testMigratesOnFirstRun {

[self setMainBundleAppVersion:@"1.0"];

XCTestExpectation *expectationBlockRun = [self expectationWithDescription:@"Should execute migration after reset"];
[MTMigration migrateToVersion:@"1.0" block:^{
Expand All @@ -58,8 +86,9 @@ - (void)testMigratesOnFirstRun
[self waitForAllExpectations];
}

- (void)testMigratesOnce
{
- (void)testMigratesOnce {

[self setMainBundleAppVersion:@"1.0"];

XCTestExpectation *expectationBlockRun = [self expectationWithDescription:@"Expecting block to be run"];
[MTMigration migrateToVersion:@"1.0" block:^{
Expand All @@ -73,8 +102,17 @@ - (void)testMigratesOnce
[self waitForAllExpectations];
}

- (void)testMigratesPreviousBlocks
{
- (void)testMigrateToVersionWontRunForLowerAppVersions {

[self setMainBundleAppVersion:@"0.1"];
[MTMigration migrateToVersion:@"1.0" block:^{
XCTFail(@"Should not execute a block for the same version twice.");
}];
}

- (void)testMigratesPreviousBlocks {

[self setMainBundleAppVersion:@"1.0"];

XCTestExpectation *expectingBlock1Run = [self expectationWithDescription:@"Expecting block to be run for version 0.9"];
[MTMigration migrateToVersion:@"0.9" block:^{
Expand All @@ -92,6 +130,8 @@ - (void)testMigratesPreviousBlocks
- (void)testMigratesInNaturalSortOrder
{

[self setMainBundleAppVersion:@"1.0"];

XCTestExpectation *expectingBlock1Run = [self expectationWithDescription:@"Expecting block to be run for version 0.9"];
[MTMigration migrateToVersion:@"0.9" block:^{
[expectingBlock1Run fulfill];
Expand All @@ -109,9 +149,12 @@ - (void)testMigratesInNaturalSortOrder
[self waitForAllExpectations];
}

- (void)testRunsApplicationUpdateBlockOnce
{
#pragma mark - Test applicationUpdateBlock

- (void)testRunsApplicationUpdateBlockOnce {

[self setMainBundleAppVersion:@"1.0"];

XCTestExpectation *expectationBlockRun = [self expectationWithDescription:@"Should only call block once"];
[MTMigration applicationUpdateBlock:^{
[expectationBlockRun fulfill];
Expand All @@ -124,9 +167,10 @@ - (void)testRunsApplicationUpdateBlockOnce
[self waitForAllExpectations];
}

- (void)testRunsApplicationUpdateBlockeOnlyOnceWithMultipleMigrations
{
- (void)testRunsApplicationUpdateBlockeOnlyOnceWithMultipleMigrations {

[self setMainBundleAppVersion:@"1.0"];

[MTMigration migrateToVersion:@"0.8" block:^{
// Do something
}];
Expand All @@ -147,11 +191,77 @@ - (void)testRunsApplicationUpdateBlockeOnlyOnceWithMultipleMigrations
[self waitForAllExpectations];
}

- (void)testRunsApplicationBlockButIgnoresFirstTimeInstall {

[self setMainBundleAppVersion:@"1.0"];

[MTMigration applicationUpdateBlock:^{
XCTFail(@"Expected on first install, this block will never run.");
} ignoreFirstInstall:YES];

[self setMainBundleAppVersion:@"1.1"];

XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block to be run"];
[MTMigration applicationUpdateBlock:^{
[expectation fulfill];
} ignoreFirstInstall:YES];

[self waitForAllExpectations];
}

- (void)waitForAllExpectations {

[self waitForExpectationsWithTimeout:kDefaultWaitForExpectionsTimeout handler:^(NSError *error) {
//do nothing
}];
}

#pragma mark - Methods used to mock [NSBundle mainBundle] values
static NSString *mockAppVersion;
static NSString *mockAppBuild;

- (void)setMainBundleAppVersion:(NSString *)appVersion {
mockAppVersion = appVersion;
}

- (void)setMainBundleAppBuild:(NSString *)appBuild {
mockAppBuild = appBuild;
}

// The following methods use a technique called method swizzling (http://nshipster.com/method-swizzling/) and
// provide the only way in which the tests can manipulate the application version and build number in our static class
//
// In summary, the method `objectForInfoDictionaryKey` is swapped with this class's method `swizzled_objectForInfoDictionaryKey`
// allowing the test method to intercept this method call and provide appropriate mock responses.

- (void)swapOutBundleMethods {

method_exchangeImplementations(class_getInstanceMethod([NSBundle class], @selector(objectForInfoDictionaryKey:)),
class_getInstanceMethod([MTMigrationTests class], @selector(swizzled_objectForInfoDictionaryKey:)));
}

- (void)swapInBundleMethods {

method_exchangeImplementations(class_getInstanceMethod([MTMigrationTests class], @selector(swizzled_objectForInfoDictionaryKey:)),
class_getInstanceMethod([NSBundle class], @selector(objectForInfoDictionaryKey:)));
}

- (id)swizzled_objectForInfoDictionaryKey:(NSString *)key {

if ([key isEqualToString:kBundleVersionKey] && mockAppBuild) {

return mockAppBuild;

} else if ([key isEqualToString:kBundleShortVersionStringKey] && mockAppVersion) {

return mockAppVersion;

} else {

XCTFail(@"Mock bundle is missing a mock key/value pair");
}

return nil;
}

@end