Skip to content

Commit

Permalink
Merge pull request #6 from mutualmobile/tests
Browse files Browse the repository at this point in the history
Adding Tests!
  • Loading branch information
cnstoll committed Dec 13, 2014
2 parents ff10864 + 2fef616 commit f2a2095
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 15 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
##[1.0.1](https://github.com/mutualmobile/MMWormhole/milestones/1.1.0) (Saturday, December 13th, 2014)
**NEW**
* Added support for iOS 7 deployment targets. (Orta Therox)
* Added full support for NSCoding and NSKeyedArchiver. (Martin Blech)
* Added Unit Tests! (Conrad Stoll)

**Fixed**
* **FIXED** an issue([#6](https://github.com/mutualmobile/MMWormhole/pull/6)) where clear all message contents wasn't working properly. (Conrad Stoll)


##1.0.0 (Monday, December 8th, 2014)
* Initial Library Release
4 changes: 4 additions & 0 deletions Example/MMWormhole/MMWormhole.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@
};
2679DBFC1A33C45200961787 = {
CreatedOnToolsVersion = 6.2;
DevelopmentTeam = RLNA7ZSN7E;
};
55D935F11A38A4F200AD1A1C = {
CreatedOnToolsVersion = 6.2;
Expand Down Expand Up @@ -730,6 +731,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_IDENTITY = "iPhone Developer";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
Expand All @@ -748,6 +750,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_IDENTITY = "iPhone Developer";
IBSC_MODULE = MMWormhole_WatchKit_Extension;
INFOPLIST_FILE = "MMWormhole Watch App/Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -876,6 +879,7 @@
55D936011A38A4F200AD1A1C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
Expand Down
218 changes: 210 additions & 8 deletions Example/MMWormhole/MMWormholeTests/MMWormholeTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>

#import "MMWormhole.h"

@interface MMWormhole (TextExtension)

- (NSString *)messagePassingDirectoryPath;
- (NSString *)filePathForIdentifier:(NSString *)identifier;
- (void)writeMessageObject:(id)messageObject toFileWithIdentifier:(NSString *)identifier;
- (id)messageObjectFromFileWithIdentifier:(NSString *)identifier;
- (void)deleteFileForIdentifier:(NSString *)identifier;
- (id)listenerBlockForIdentifier:(NSString *)identifier;

@end

@interface MMWormholeTests : XCTestCase

@end
Expand All @@ -17,24 +30,213 @@ @implementation MMWormholeTests

- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}

- (void)testExample {
// This is an example of a functional test case.
XCTAssert(YES, @"Pass");
- (void)testMessagePassingDirectory {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];
NSString *messagePassingDirectoryPath = [wormhole messagePassingDirectoryPath];

NSString *lastComponent = [[messagePassingDirectoryPath pathComponents] lastObject];

XCTAssert([lastComponent isEqualToString:@"testDirectory"], @"Message Passing Directory path should contain optional directory.");
}

- (void)testFilePathForIdentifier {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];
NSString *filePathForIdentifier = [wormhole filePathForIdentifier:@"testIdentifier"];

NSString *lastComponent = [[filePathForIdentifier pathComponents] lastObject];

XCTAssert([lastComponent isEqualToString:@"testIdentifier.archive"], @"File Path Identifier should equal the passed identifier with the .archive extension.");
}

- (void)testFilePathForNilIdentifier {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];
NSString *filePathForIdentifier = [wormhole filePathForIdentifier:nil];

NSString *lastComponent = [[filePathForIdentifier pathComponents] lastObject];

XCTAssertNil(lastComponent, @"File Path Identifier should be nil if no identifier is provided.");
}

- (void)testPassingMessageWithNilIdentifier {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];

[wormhole passMessageObject:@{} identifier:nil];

XCTAssertTrue(YES, @"Message Passing should not crash for nil message identifiers.");
}

- (void)testValidMessagePassing {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];

[wormhole deleteFileForIdentifier:@"testIdentifier"];

id messageObject = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier"];

XCTAssertNil(messageObject, @"Message object should be nil after deleting file.");

NSString *filePathForIdentifier = [wormhole filePathForIdentifier:@"testIdentifier"];

[wormhole passMessageObject:@{} identifier:@"testIdentifier"];

NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier];

XCTAssertNotNil(data, @"Message contents should not be nil after passing a valid message.");
}

- (void)testFileWriting {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];

[wormhole deleteFileForIdentifier:@"testIdentifier"];

id messageObject = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier"];

XCTAssertNil(messageObject, @"Message object should be nil after deleting file.");

NSString *filePathForIdentifier = [wormhole filePathForIdentifier:@"testIdentifier"];

[wormhole writeMessageObject:@{} toFileWithIdentifier:@"testIdentifier"];

NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier];

XCTAssertNotNil(data, @"Message contents should not be nil after writing a valid message to disk.");
}

- (void)testClearingIndividualMessage {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];

[wormhole passMessageObject:@{} identifier:@"testIdentifier"];

id messageObject = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier"];

XCTAssertNotNil(messageObject, @"Message contents should not be nil after passing a valid message.");

[wormhole clearMessageContentsForIdentifier:@"testIdentifier"];

NSString *filePathForIdentifier = [wormhole filePathForIdentifier:@"testIdentifier"];

NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier];

XCTAssertNil(data, @"Message file should be gone after deleting message.");

id deletedMessageObject = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier"];

XCTAssertNil(deletedMessageObject, @"Message object should be nil after deleting message.");
}

- (void)testClearingAllMessages {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];

[wormhole passMessageObject:@{} identifier:@"testIdentifier1"];
[wormhole passMessageObject:@{} identifier:@"testIdentifier2"];
[wormhole passMessageObject:@{} identifier:@"testIdentifier3"];

[wormhole clearAllMessageContents];

id deletedMessageObject1 = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier1"];
id deletedMessageObject2 = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier2"];
id deletedMessageObject3 = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier3"];

XCTAssertNil(deletedMessageObject1, @"Message object should be nil after deleting message.");
XCTAssertNil(deletedMessageObject2, @"Message object should be nil after deleting message.");
XCTAssertNil(deletedMessageObject3, @"Message object should be nil after deleting message.");
}

- (void)testMessageListening {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];

XCTestExpectation *expectation = [self expectationWithDescription:@"Listener should hear something"];

[wormhole listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) {
XCTAssertNotNil(messageObject, @"Valid message object should not be nil.");

[expectation fulfill];
}];

// Simulate a fake notification since Darwin notifications aren't delivered to the sender

[[NSNotificationCenter defaultCenter] postNotificationName:@"MMWormholeNotificationName"
object:nil
userInfo:@{@"identifier" : @"testIdentifier"}];

[self waitForExpectationsWithTimeout:2 handler:nil];
}

- (void)testStopMessageListening {
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];

XCTestExpectation *expectation = [self expectationWithDescription:@"Listener should hear something"];

[wormhole listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) {
XCTAssertNotNil(messageObject, @"Valid message object should not be nil.");

[expectation fulfill];
}];

// Simulate a fake notification since Darwin notifications aren't delivered to the sender

[[NSNotificationCenter defaultCenter] postNotificationName:@"MMWormholeNotificationName"
object:nil
userInfo:@{@"identifier" : @"testIdentifier"}];

[self waitForExpectationsWithTimeout:2 handler:^(NSError *error) {
id listenerBlock = [wormhole listenerBlockForIdentifier:@"testIdentifier"];

XCTAssertNotNil(listenerBlock, @"A valid listener block should not be nil.");

[wormhole stopListeningForMessageWithIdentifier:@"testIdentifier"];

id deletedListenerBlock = [wormhole listenerBlockForIdentifier:@"testIdentifier"];

XCTAssertNil(deletedListenerBlock, @"The listener block should be nil after you stop listening.");
}];
}

- (void)testMessagePassingPerformance {
[self measureBlock:^{
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
optionalDirectory:@"testDirectory"];

[wormhole passMessageObject:[self performanceSampleJSONObject] identifier:@"testPerformance"];
}];
}

- (void)testJSONPerformance {
[self measureBlock:^{
[NSJSONSerialization dataWithJSONObject:[self performanceSampleJSONObject] options:0 error:NULL];
}];
}

- (void)testPerformanceExample {
// This is an example of a performance test case.
- (void)testArchivePerformance {
[self measureBlock:^{
// Put the code you want to measure the time of here.
[NSKeyedArchiver archivedDataWithRootObject:[self performanceSampleJSONObject]];
}];
}

- (id)performanceSampleJSONObject {
return @[@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}}
];
}

@end
2 changes: 1 addition & 1 deletion MMWormhole.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'MMWormhole'
s.version = '1.0.0'
s.version = '1.1.0'
s.license = 'MIT'
s.summary = 'Message passing between iOS apps and extensions.'
s.homepage = 'https://github.com/mutualmobile/MMWormhole'
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ MMWormhole is designed to make it easy to share very basic information and comma

A good way to think of the wormhole is a collection of shared mailboxes. An identifier is essentially a unique mailbox you can send messages to. You know where a message will be delivered to because of the identifier you associate with it, but not necessarily when the message will be picked up by the recipient. If the app or extension are in the background, they may not receive the message immediately. By convention, sending messages should be done from one side to another, not necessarily from yourself to yourself. It's also a good practice to check the contents of your mailbox when your app or extension wakes up, in case any messages have been left there while you were away.

MMWormhole uses NSKeyedArchiver as a serialization medium, so any object that is NScoding compliant can work as a message. For many apps, sharing simple strings or numbers is sufficient to drive the UI of a Widget or Apple Watch app. Messages can be sent and persisted easily as archive files and read later when the app or extension is woken up later.
MMWormhole uses NSKeyedArchiver as a serialization medium, so any object that is NSCoding compliant can work as a message. For many apps, sharing simple strings, numbers, or JSON objects is sufficient to drive the UI of a Widget or Apple Watch app. Messages can be sent and persisted easily as archive files and read later when the app or extension is woken up later.

Using MMWormhole is extremely straightforward. The only real catch is that your app and it's extensions must support shared app groups. The group will be used for writing the archive files that represent each message. While larger files and structures, including a whole Core Data database, can be shared using App Groups, MMWormhole is designed to use it's own directory simply to pass messages. Because of that, a best practice is to initialize MMWormhole with a directory name that it will use within your app's shared App Group.

Expand Down Expand Up @@ -85,6 +85,10 @@ You can also listen for changes to that message and be notified when that messag
```

### Designing Your Communication Scheme

You can think of message passing between apps and extensions sort of like a web service. The web service has endpoints that you can read and write. The message identifiers for your MMWormhole messages can be thought of in much the same way. A great practice is to design very clear message identifiers so that you immediately know when reading your code who sent the message and why, and what the possible contents of the message might be. Just like you would design a web service with clear semantics, you should do the same with your wormhole messaging scheme.


## Requirements

Expand Down
2 changes: 1 addition & 1 deletion Source/MMWormhole.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
Passing a message to the wormhole can be inferred as a data transfer package or as a command. In
both cases, the passed message is archived using NSKeyedArchiver to a .archive file named with the
included identifier. As a data transfer, the contents of the written .archive file can be queried using
included identifier. Once passed, the contents of the written .archive file can be queried using
the messageWithIdentifier: method. As a command, the simple existence of the message in the shared
app group should be taken as proof of the command's invocation. The contents of the message then
become parameters to be evaluated along with the command. Of course, to avoid confusion later, it
Expand Down
21 changes: 17 additions & 4 deletions Source/MMWormhole.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ - (NSString *)messagePassingDirectoryPath {
}

- (NSString *)filePathForIdentifier:(NSString *)identifier {
if (identifier == nil || identifier.length == 0) {
return nil;
}

NSString *directoryPath = [self messagePassingDirectoryPath];
NSString *fileName = [NSString stringWithFormat:@"%@.archive", identifier];
NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
Expand All @@ -100,12 +104,13 @@ - (void)writeMessageObject:(id)messageObject toFileWithIdentifier:(NSString *)id
}

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject];
NSString *filePath = [self filePathForIdentifier:identifier];

if (data == nil) {
if (data == nil || filePath == nil) {
return;
}

BOOL success = [data writeToFile:[self filePathForIdentifier:identifier] atomically:YES];
BOOL success = [data writeToFile:filePath atomically:YES];

if (success) {
[self sendNotificationForMessageWithIdentifier:identifier];
Expand Down Expand Up @@ -181,7 +186,7 @@ - (void)didReceiveMessageNotification:(NSNotification *)notification {
NSString *identifier = [userInfo valueForKey:@"identifier"];

if (identifier != nil) {
MessageListenerBlock listenerBlock = [self.listenerBlocks valueForKey:identifier];
MessageListenerBlock listenerBlock = [self listenerBlockForIdentifier:identifier];

if (listenerBlock) {
id messageObject = [self messageObjectFromFileWithIdentifier:identifier];
Expand All @@ -191,6 +196,10 @@ - (void)didReceiveMessageNotification:(NSNotification *)notification {
}
}

- (id)listenerBlockForIdentifier:(NSString *)identifier {
return [self.listenerBlocks valueForKey:identifier];
}


#pragma mark - Public Interface Methods

Expand All @@ -213,8 +222,12 @@ - (void)clearAllMessageContents {
if (self.directory != nil) {
NSArray *messageFiles = [self.fileManager contentsOfDirectoryAtPath:[self messagePassingDirectoryPath] error:NULL];

NSString *directoryPath = [self messagePassingDirectoryPath];

for (NSString *path in messageFiles) {
[self.fileManager removeItemAtPath:path error:NULL];
NSString *filePath = [directoryPath stringByAppendingPathComponent:path];

[self.fileManager removeItemAtPath:filePath error:NULL];
}
}
}
Expand Down

0 comments on commit f2a2095

Please sign in to comment.