Skip to content

Commit f2a2095

Browse files
committed
Merge pull request #6 from mutualmobile/tests
Adding Tests!
2 parents ff10864 + 2fef616 commit f2a2095

7 files changed

Lines changed: 248 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
1+
##[1.0.1](https://github.com/mutualmobile/MMWormhole/milestones/1.1.0) (Saturday, December 13th, 2014)
2+
**NEW**
3+
* Added support for iOS 7 deployment targets. (Orta Therox)
4+
* Added full support for NSCoding and NSKeyedArchiver. (Martin Blech)
5+
* Added Unit Tests! (Conrad Stoll)
6+
7+
**Fixed**
8+
* **FIXED** an issue([#6](https://github.com/mutualmobile/MMWormhole/pull/6)) where clear all message contents wasn't working properly. (Conrad Stoll)
9+
10+
111
##1.0.0 (Monday, December 8th, 2014)
212
* Initial Library Release

Example/MMWormhole/MMWormhole.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@
424424
};
425425
2679DBFC1A33C45200961787 = {
426426
CreatedOnToolsVersion = 6.2;
427+
DevelopmentTeam = RLNA7ZSN7E;
427428
};
428429
55D935F11A38A4F200AD1A1C = {
429430
CreatedOnToolsVersion = 6.2;
@@ -730,6 +731,7 @@
730731
buildSettings = {
731732
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
732733
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
734+
CODE_SIGN_IDENTITY = "iPhone Developer";
733735
GCC_PREPROCESSOR_DEFINITIONS = (
734736
"DEBUG=1",
735737
"$(inherited)",
@@ -748,6 +750,7 @@
748750
buildSettings = {
749751
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
750752
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
753+
CODE_SIGN_IDENTITY = "iPhone Developer";
751754
IBSC_MODULE = MMWormhole_WatchKit_Extension;
752755
INFOPLIST_FILE = "MMWormhole Watch App/Info.plist";
753756
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -876,6 +879,7 @@
876879
55D936011A38A4F200AD1A1C /* Release */,
877880
);
878881
defaultConfigurationIsVisible = 0;
882+
defaultConfigurationName = Release;
879883
};
880884
/* End XCConfigurationList section */
881885
};

Example/MMWormhole/MMWormholeTests/MMWormholeTests.m

Lines changed: 210 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@
99
#import <UIKit/UIKit.h>
1010
#import <XCTest/XCTest.h>
1111

12+
#import "MMWormhole.h"
13+
14+
@interface MMWormhole (TextExtension)
15+
16+
- (NSString *)messagePassingDirectoryPath;
17+
- (NSString *)filePathForIdentifier:(NSString *)identifier;
18+
- (void)writeMessageObject:(id)messageObject toFileWithIdentifier:(NSString *)identifier;
19+
- (id)messageObjectFromFileWithIdentifier:(NSString *)identifier;
20+
- (void)deleteFileForIdentifier:(NSString *)identifier;
21+
- (id)listenerBlockForIdentifier:(NSString *)identifier;
22+
23+
@end
24+
1225
@interface MMWormholeTests : XCTestCase
1326

1427
@end
@@ -17,24 +30,213 @@ @implementation MMWormholeTests
1730

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

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

28-
- (void)testExample {
29-
// This is an example of a functional test case.
30-
XCTAssert(YES, @"Pass");
39+
- (void)testMessagePassingDirectory {
40+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
41+
optionalDirectory:@"testDirectory"];
42+
NSString *messagePassingDirectoryPath = [wormhole messagePassingDirectoryPath];
43+
44+
NSString *lastComponent = [[messagePassingDirectoryPath pathComponents] lastObject];
45+
46+
XCTAssert([lastComponent isEqualToString:@"testDirectory"], @"Message Passing Directory path should contain optional directory.");
47+
}
48+
49+
- (void)testFilePathForIdentifier {
50+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
51+
optionalDirectory:@"testDirectory"];
52+
NSString *filePathForIdentifier = [wormhole filePathForIdentifier:@"testIdentifier"];
53+
54+
NSString *lastComponent = [[filePathForIdentifier pathComponents] lastObject];
55+
56+
XCTAssert([lastComponent isEqualToString:@"testIdentifier.archive"], @"File Path Identifier should equal the passed identifier with the .archive extension.");
57+
}
58+
59+
- (void)testFilePathForNilIdentifier {
60+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
61+
optionalDirectory:@"testDirectory"];
62+
NSString *filePathForIdentifier = [wormhole filePathForIdentifier:nil];
63+
64+
NSString *lastComponent = [[filePathForIdentifier pathComponents] lastObject];
65+
66+
XCTAssertNil(lastComponent, @"File Path Identifier should be nil if no identifier is provided.");
67+
}
68+
69+
- (void)testPassingMessageWithNilIdentifier {
70+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
71+
optionalDirectory:@"testDirectory"];
72+
73+
[wormhole passMessageObject:@{} identifier:nil];
74+
75+
XCTAssertTrue(YES, @"Message Passing should not crash for nil message identifiers.");
76+
}
77+
78+
- (void)testValidMessagePassing {
79+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
80+
optionalDirectory:@"testDirectory"];
81+
82+
[wormhole deleteFileForIdentifier:@"testIdentifier"];
83+
84+
id messageObject = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier"];
85+
86+
XCTAssertNil(messageObject, @"Message object should be nil after deleting file.");
87+
88+
NSString *filePathForIdentifier = [wormhole filePathForIdentifier:@"testIdentifier"];
89+
90+
[wormhole passMessageObject:@{} identifier:@"testIdentifier"];
91+
92+
NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier];
93+
94+
XCTAssertNotNil(data, @"Message contents should not be nil after passing a valid message.");
95+
}
96+
97+
- (void)testFileWriting {
98+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
99+
optionalDirectory:@"testDirectory"];
100+
101+
[wormhole deleteFileForIdentifier:@"testIdentifier"];
102+
103+
id messageObject = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier"];
104+
105+
XCTAssertNil(messageObject, @"Message object should be nil after deleting file.");
106+
107+
NSString *filePathForIdentifier = [wormhole filePathForIdentifier:@"testIdentifier"];
108+
109+
[wormhole writeMessageObject:@{} toFileWithIdentifier:@"testIdentifier"];
110+
111+
NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier];
112+
113+
XCTAssertNotNil(data, @"Message contents should not be nil after writing a valid message to disk.");
114+
}
115+
116+
- (void)testClearingIndividualMessage {
117+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
118+
optionalDirectory:@"testDirectory"];
119+
120+
[wormhole passMessageObject:@{} identifier:@"testIdentifier"];
121+
122+
id messageObject = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier"];
123+
124+
XCTAssertNotNil(messageObject, @"Message contents should not be nil after passing a valid message.");
125+
126+
[wormhole clearMessageContentsForIdentifier:@"testIdentifier"];
127+
128+
NSString *filePathForIdentifier = [wormhole filePathForIdentifier:@"testIdentifier"];
129+
130+
NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier];
131+
132+
XCTAssertNil(data, @"Message file should be gone after deleting message.");
133+
134+
id deletedMessageObject = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier"];
135+
136+
XCTAssertNil(deletedMessageObject, @"Message object should be nil after deleting message.");
137+
}
138+
139+
- (void)testClearingAllMessages {
140+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
141+
optionalDirectory:@"testDirectory"];
142+
143+
[wormhole passMessageObject:@{} identifier:@"testIdentifier1"];
144+
[wormhole passMessageObject:@{} identifier:@"testIdentifier2"];
145+
[wormhole passMessageObject:@{} identifier:@"testIdentifier3"];
146+
147+
[wormhole clearAllMessageContents];
148+
149+
id deletedMessageObject1 = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier1"];
150+
id deletedMessageObject2 = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier2"];
151+
id deletedMessageObject3 = [wormhole messageObjectFromFileWithIdentifier:@"testIdentifier3"];
152+
153+
XCTAssertNil(deletedMessageObject1, @"Message object should be nil after deleting message.");
154+
XCTAssertNil(deletedMessageObject2, @"Message object should be nil after deleting message.");
155+
XCTAssertNil(deletedMessageObject3, @"Message object should be nil after deleting message.");
156+
}
157+
158+
- (void)testMessageListening {
159+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
160+
optionalDirectory:@"testDirectory"];
161+
162+
XCTestExpectation *expectation = [self expectationWithDescription:@"Listener should hear something"];
163+
164+
[wormhole listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) {
165+
XCTAssertNotNil(messageObject, @"Valid message object should not be nil.");
166+
167+
[expectation fulfill];
168+
}];
169+
170+
// Simulate a fake notification since Darwin notifications aren't delivered to the sender
171+
172+
[[NSNotificationCenter defaultCenter] postNotificationName:@"MMWormholeNotificationName"
173+
object:nil
174+
userInfo:@{@"identifier" : @"testIdentifier"}];
175+
176+
[self waitForExpectationsWithTimeout:2 handler:nil];
177+
}
178+
179+
- (void)testStopMessageListening {
180+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
181+
optionalDirectory:@"testDirectory"];
182+
183+
XCTestExpectation *expectation = [self expectationWithDescription:@"Listener should hear something"];
184+
185+
[wormhole listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) {
186+
XCTAssertNotNil(messageObject, @"Valid message object should not be nil.");
187+
188+
[expectation fulfill];
189+
}];
190+
191+
// Simulate a fake notification since Darwin notifications aren't delivered to the sender
192+
193+
[[NSNotificationCenter defaultCenter] postNotificationName:@"MMWormholeNotificationName"
194+
object:nil
195+
userInfo:@{@"identifier" : @"testIdentifier"}];
196+
197+
[self waitForExpectationsWithTimeout:2 handler:^(NSError *error) {
198+
id listenerBlock = [wormhole listenerBlockForIdentifier:@"testIdentifier"];
199+
200+
XCTAssertNotNil(listenerBlock, @"A valid listener block should not be nil.");
201+
202+
[wormhole stopListeningForMessageWithIdentifier:@"testIdentifier"];
203+
204+
id deletedListenerBlock = [wormhole listenerBlockForIdentifier:@"testIdentifier"];
205+
206+
XCTAssertNil(deletedListenerBlock, @"The listener block should be nil after you stop listening.");
207+
}];
208+
}
209+
210+
- (void)testMessagePassingPerformance {
211+
[self measureBlock:^{
212+
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole"
213+
optionalDirectory:@"testDirectory"];
214+
215+
[wormhole passMessageObject:[self performanceSampleJSONObject] identifier:@"testPerformance"];
216+
}];
217+
}
218+
219+
- (void)testJSONPerformance {
220+
[self measureBlock:^{
221+
[NSJSONSerialization dataWithJSONObject:[self performanceSampleJSONObject] options:0 error:NULL];
222+
}];
31223
}
32224

33-
- (void)testPerformanceExample {
34-
// This is an example of a performance test case.
225+
- (void)testArchivePerformance {
35226
[self measureBlock:^{
36-
// Put the code you want to measure the time of here.
227+
[NSKeyedArchiver archivedDataWithRootObject:[self performanceSampleJSONObject]];
37228
}];
38229
}
39230

231+
- (id)performanceSampleJSONObject {
232+
return @[@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
233+
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
234+
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
235+
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
236+
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
237+
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}},
238+
@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}}
239+
];
240+
}
241+
40242
@end

MMWormhole.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'MMWormhole'
3-
s.version = '1.0.0'
3+
s.version = '1.1.0'
44
s.license = 'MIT'
55
s.summary = 'Message passing between iOS apps and extensions.'
66
s.homepage = 'https://github.com/mutualmobile/MMWormhole'

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ MMWormhole is designed to make it easy to share very basic information and comma
4444

4545
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.
4646

47-
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.
47+
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.
4848

4949
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.
5050

@@ -85,6 +85,10 @@ You can also listen for changes to that message and be notified when that messag
8585
8686
```
8787

88+
### Designing Your Communication Scheme
89+
90+
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.
91+
8892

8993
## Requirements
9094

Source/MMWormhole.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
4242
Passing a message to the wormhole can be inferred as a data transfer package or as a command. In
4343
both cases, the passed message is archived using NSKeyedArchiver to a .archive file named with the
44-
included identifier. As a data transfer, the contents of the written .archive file can be queried using
44+
included identifier. Once passed, the contents of the written .archive file can be queried using
4545
the messageWithIdentifier: method. As a command, the simple existence of the message in the shared
4646
app group should be taken as proof of the command's invocation. The contents of the message then
4747
become parameters to be evaluated along with the command. Of course, to avoid confusion later, it

Source/MMWormhole.m

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ - (NSString *)messagePassingDirectoryPath {
8787
}
8888

8989
- (NSString *)filePathForIdentifier:(NSString *)identifier {
90+
if (identifier == nil || identifier.length == 0) {
91+
return nil;
92+
}
93+
9094
NSString *directoryPath = [self messagePassingDirectoryPath];
9195
NSString *fileName = [NSString stringWithFormat:@"%@.archive", identifier];
9296
NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
@@ -100,12 +104,13 @@ - (void)writeMessageObject:(id)messageObject toFileWithIdentifier:(NSString *)id
100104
}
101105

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

104-
if (data == nil) {
109+
if (data == nil || filePath == nil) {
105110
return;
106111
}
107112

108-
BOOL success = [data writeToFile:[self filePathForIdentifier:identifier] atomically:YES];
113+
BOOL success = [data writeToFile:filePath atomically:YES];
109114

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

183188
if (identifier != nil) {
184-
MessageListenerBlock listenerBlock = [self.listenerBlocks valueForKey:identifier];
189+
MessageListenerBlock listenerBlock = [self listenerBlockForIdentifier:identifier];
185190

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

199+
- (id)listenerBlockForIdentifier:(NSString *)identifier {
200+
return [self.listenerBlocks valueForKey:identifier];
201+
}
202+
194203

195204
#pragma mark - Public Interface Methods
196205

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

225+
NSString *directoryPath = [self messagePassingDirectoryPath];
226+
216227
for (NSString *path in messageFiles) {
217-
[self.fileManager removeItemAtPath:path error:NULL];
228+
NSString *filePath = [directoryPath stringByAppendingPathComponent:path];
229+
230+
[self.fileManager removeItemAtPath:filePath error:NULL];
218231
}
219232
}
220233
}

0 commit comments

Comments
 (0)